目录
1.Android为什么要将Finalize机制替换成Cleaner机制?
1.Android为什么要将Finalize机制替换成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对象很大,则需要显示调用 |
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