目录
思考问题
1.Android为什么要将Finalize机制替换成Cleaner机制?
2.Cleaner机制回收Native堆内存的原理是什么?
3.Cleaner机制源码是如何实现的?
一、版本
二、类图
三、流程
1.Bitmap对象注册Native堆内存资源
分析一
2.达到内存阈值时,触发GC流程
2.2 后GC阶段
四、问题
1.Android为什么要将Finalize机制替换成Cleaner机制?
2.Cleaner机制回收Native堆内存的原理是什么?
3.Cleaner机制源码是如何实现的?
五、总结
六、学习到了什么
七、参考
基于Andrroid 11(R)
Bitmap的构造函数是通过called from JNI回调的,所以我们能在构造函数中拿到native堆内存的对象指针
/** * Private constructor that must received an already allocated native bitmap * int (pointer). */ // called from JNI Bitmap(long nativeBitmap, int width, int height, int density, boolean isMutable, boolean requestPremultiplied, byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { //nativeBitmap 回传的native对象地址 if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } mWidth = width; mHeight = height; mIsMutable = isMutable; mRequestPremultiplied = requestPremultiplied; mNinePatchChunk = ninePatchChunk; mNinePatchInsets = ninePatchInsets; if (density >= 0) { mDensity = density; } mNativePtr = nativeBitmap; //native堆内存大小 long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); NativeAllocationRegistry registry = new NativeAllocationRegistry( Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); //关联本地内存对象 registry.registerNativeAllocation(this, nativeBitmap); if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) { sPreloadTracingNumInstantiatedBitmaps++; sPreloadTracingTotalBitmapsSize += nativeSize; } }
// NativeAllocationRegistry.java public class NativeAllocationRegistry { // 加载 freeFunction 函数的类加载器 private final ClassLoader classLoader; // 回收 native 内存的 native 函数直接地址 private final long freeFunction; // 分配的 native 内存大小(字节) private final long size; public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) { if (size < 0) { throw new IllegalArgumentException("Invalid native allocation size: " + size); } this.classLoader = classLoader; //分析1 this.freeFunction = freeFunction; this.size = size; } }
natviceGetNativeFinalizer() 方法主要是传递native对象的释放资源的析构函数的位置
// Bitmap.java // 【分析点 2:回收函数 nativeGetNativeFinalizer()】 NativeAllocationRegistry registry = new NativeAllocationRegistry( Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); private static native long nativeGetNativeFinalizer(); // Java 层 // ---------------------------------------------------------------------- // native 层 // Bitmap.cpp static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) { // 转为long Bitmap_destruct是回收native层的 return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct)); } static void Bitmap_destruct(BitmapWrapper* bitmap) { delete bitmap; }
// Bitmap.java // 注册 Java 层对象引用与 native 层对象的地址 registry.registerNativeAllocation(this, nativeBitmap); // NativeAllocationRegistry.java public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { throw new IllegalArgumentException("referent is null"); } if (nativePtr == 0) { throw new IllegalArgumentException("nativePtr is null"); } CleanerThunk thunk; CleanerRunner result; try { //thunk对象实现Runnable接口 thunk = new CleanerThunk(); //创建Cleaner对象并传递该引用对象和thunk任务对象 Cleaner cleaner = Cleaner.create(referent, thunk); result = new CleanerRunner(cleaner); //给ART虚拟机注册本地内存的大小,GC内存大小统一在java层 registerNativeAllocation(this.size); } catch (VirtualMachineError vme /* probably OutOfMemoryError */) { applyFreeFunction(freeFunction, nativePtr); throw vme; // Other exceptions are impossible. // Enable the cleaner only after we can no longer throw anything, including OOME. //thunk设置本地指针 thunk.setNativePtr(nativePtr); return result; }
2.1Mark阶段
art/runtime/gc/collector/concurrent_copying.cc
2205 inline void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { ... 2292 if (perform_scan) { 2293 if (use_generational_cc_ && young_gen_) { //扫码年轻代里面的引用 2294 Scan<true>(to_ref); 2295 } else { 2296 Scan<false>(to_ref); 2297 } 2298 }
art/runtime/gc/reference_processor.cc
232 // Process the "referent" field in a java.lang.ref.Reference. If the referent has not yet been 233 // marked, put it on the appropriate list in the heap for later processing. 234 void ReferenceProcessor::DelayReferenceReferent(ObjPtr<mirror::Class> klass, ... 243 if (!collector->IsNullOrMarkedHeapReference(referent, /*do_atomic_update=*/true)) { <==== 如果referent未被标记,则表明其将被回收 ... 257 if (klass->IsSoftReferenceClass()) { 258 soft_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); 259 } else if (klass->IsWeakReferenceClass()) { 260 weak_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); 261 } else if (klass->IsFinalizerReferenceClass()) { 262 finalizer_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); 263 } else if (klass->IsPhantomReferenceClass()) { <============== 【关键】如果当前reference为PhantomReference,则将其加入到native的phantom_reference_queue_中 264 phantom_reference_queue_.AtomicEnqueueIfNotEnqueued(self, ref); 265 } else { 266 LOG(FATAL) << "Invalid reference type " << klass->PrettyClass() << " " << std::hex 267 << klass->GetAccessFlags(); 268 } 269 } 270 }
art/runtime/gc/reference_processor.cc
281 void Run(Thread* thread) override { 282 ScopedObjectAccess soa(thread); 283 jvalue args[1]; 284 args[0].l = cleared_references_; 285 InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_ReferenceQueue_add, args); <===== 调用Java方法 286 soa.Env()->DeleteGlobalRef(cleared_references_); 287 }
libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java
static void add(Reference<?> list) { 262 synchronized (ReferenceQueue.class) { 263 if (unenqueued == null) { 264 unenqueued = list; 265 } else { 266 // Find the last element in unenqueued. 267 Reference<?> last = unenqueued; 268 while (last.pendingNext != unenqueued) { 269 last = last.pendingNext; 270 } 271 // Add our list to the end. Update the pendingNext to point back to enqueued. 272 last.pendingNext = list; 273 last = list; 274 while (last.pendingNext != list) { 275 last = last.pendingNext; 276 } 277 last.pendingNext = unenqueued; 278 } 279 ReferenceQueue.class.notifyAll(); //当cleared_references_中所有元素都添加进Java的全局ReferenceQueue中后,调用notifyAll唤醒ReferenceQueueDaemon线程 280 } 281 }
后续流程参考:
https://juejin.cn/post/6891918738846105614
【】因为Finalize机制存在上述说的3个缺陷,会引用native回收在并发访问时出错,所以在Java 9替换成了Cleaner机制
【】通过Cleaner类继承PhantomReference对象,所以就用了PhantomReference引用的特性-》在GC回收时,当对象只关联PhantomReference对象时,
会加入到PhantomReferenceQueue中去,进而进行自定义的Native内存资源释放
【】参考上面的流程
Finalized机制 | Cleaner机制 | NativeAllocationRegistry | |
原理 | 利用GC回收内存时,Object会调用finalize()方法,
在Object被回收时,重写finalize()方法里面去释放本地资源 参考:Bitmap (Android 7.0之前的实现)
| Cleaner类继承PhantomReference类,利用虚引用特性,当referent对象引用不可达时,会把该referent对象关联的PhantomReference放入到特定的ReferenceQueue中。 然后在ReferenceQueue中去执行Cleaner中的clean()方法,去执行native内存的释放操作 | 与Cleaner机制一致 |
优点 | 1.实现简单 | 1.在虚引用的全局Daemons守护线程中对虚引用队列操作,不会出现FInalized机制的并发问题 | Android N开始引入NativeAllocationRegistry类,一方面简化Cleaner的使用,另一方面将native资源的大小计入GC触发的策略中 |
缺点 | 1. 如果两个对象同时变成unreachable,他们的finalize方法执行顺序是任意的。因此在一个对象的finalize方法中使用另一个对象持有的native指针,将有可能访问一个已经释放的C++对象,从而导致native heap corruption。 | ||
2. 如果Java对象很小,而持有的native对象很大,则需要显示调用 | 2. 如果Java对象很小,而持有的native对象很大,则需要显示调用 |
设计 | 1.PhantomReference虚引用特性 ——》对象加入到PhantomReferenceQueue之后,使用queue.get()方法是获取不到该虚引用的 |
2.使用PhantoReference虚引用特性——》当GC回收资源时,在PhantomReference引用队列会在自己自定义的线程执行 | |
3.NativeAllocationRegistry类是Android N(7.0) 引入,主要是Java实例对象在Native堆内存管理和释放的类 |
https://juejin.cn/post/6891918738846105614
https://www.jianshu.com/p/6f042f9e47a8
https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/NativeAllocationRegistry.java