C/C++教程

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

本文主要是介绍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)

二、类图 

 

 

三、流程

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

这篇关于ART虚拟机 | Cleaner机制源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!