ART虚拟机 | Cleaner机制源码分析

目录

思考问题

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机制源码是如何实现的?

五、总结

 

 

 

 

 

 

 

 

 

 

 

 

 

六、学习到了什么

 

 

 

 

七、参考


思考问题

1.Android为什么要将Finalize机制替换成Cleaner机制?

2.Cleaner机制回收Native堆内存的原理是什么?

3.Cleaner机制源码是如何实现的?

 

一、版本

基于Andrroid 11(R)

二、类图 

ART虚拟机 | Cleaner机制源码分析

 

 

三、流程

1.Bitmap对象注册Native堆内存资源

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.达到内存阈值时,触发GC流程

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 }

2.2 后GC阶段

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

 

四、问题

1.Android为什么要将Finalize机制替换成Cleaner机制?

【】因为Finalize机制存在上述说的3个缺陷,会引用native回收在并发访问时出错,所以在Java 9替换成了Cleaner机制

 

2.Cleaner机制回收Native堆内存的原理是什么?

【】通过Cleaner类继承PhantomReference对象,所以就用了PhantomReference引用的特性-》在GC回收时,当对象只关联PhantomReference对象时,

会加入到PhantomReferenceQueue中去,进而进行自定义的Native内存资源释放

 

3.Cleaner机制源码是如何实现的?

【】参考上面的流程

 

五、总结

 
 

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对象很大,则需要显示调用System.gc()以提早触发GC。否则单纯依靠Java堆的增长来达到触发水位,可能要猴年马月了,而此时垃圾的native对象将堆积成山。

2.

如果Java对象很小,而持有的native对象很大,则需要显示调用System.gc()以提早触发GC。否则单纯依靠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

上一篇:MySQL基础(一)安装并配置MySQL【windows】


下一篇:Python TensorFlow报错ImportError: cannot import name 'BatchNormalization'解决方法