源码分析基于Android 11(R)
java
C++中的对象释放由程序员负责,而Java中的对象释放则由GC负责。若是一个Java对象经过指针持有native对象,那么应该什么时候释放native对象呢?靠原有的GC天然搞不定,由于虚拟机没法得知这个Java对象的long型字段是否是指针,以及该指向哪一个native对象。android
早先的作法是在Java类中实现finalize方法,该方法会在Java对象回收的时候获得调用。这样咱们即可以在finalize方法中去释放native对象,让Java资源和native资源在GC过程当中同时释放。不过finalize方法有诸多缺陷,最终在JDK 9中被弃用。替代它的是Cleaner类。c++
如何在Java对象被回收的时候,自动释放其所关联的native对象和资源?程序员
这是finalize和Cleaner想要解决的问题。纯Java层面的应用开发一般不会涉及到Java对象持有native对象指针的设计,但对一些复杂的类而言,这种设计不可或缺。譬如你们常常用到的Bitmap,就是经过这种方式将大部份内存消耗放到native堆而不是Java堆。markdown
Finalize用起来很方便。覆写一个方法,在方法里面释放资源,两步就能够搞定native资源的释放,因此也被广大开发者所喜好。但是方便有时须要付出代价。性能的牺牲是一方面,在某些场景下致使的内存错误则更加没法忍受。Android Runtime团队的大佬Hans Boehm在Google IO 2017曾就这个问题专门作过演说,里面提到的finalize的3个缺点,感兴趣的能够去油管上查看:连接,我在这里简单总结下。app
System.gc()
以提前触发GC。不然单纯依靠Java堆的增加来达到触发水位,可能要猴年马月了,而此时垃圾的native对象将堆积成山。提到Hans Boehm,我有个感触想跟你们分享下。这位前辈74年上的本科(估算65岁左右),康奈尔博士毕业,然而至今仍然奋战在项目一线,ART中不少关键代码都是他提交的。我曾经邮件向他请教过问题,他为人十分和蔼,对于像我这种菜鸡提的问题也回答得十分详细。按照国内35岁辞退的浮躁心态来看,他这么大年纪没混成个领导,还在一线写代码,真是失败。但看到他的我的简介,你还能说出这样的话么?ide
I am an ACM Fellow, and a past Chair of ACM SIGPLAN (2001-2003). Until late 2017 I chaired the ISO C++ Concurrency Study Group (WG21/SG1), where I continue to actively participate.函数
在技术领域,不少卓越的贡献是须要时间来沉淀的。固然对于业务而言,技术的深度并不会在早期获利,所以时常被人忽略。但我相信随着国力的提高,那些沉下心来深耕的人总会获得回报。由于业务的红利是有技术创新这个上限的。技术创新须要务实,而浮躁的土壤只能滋生出概念和骗局。oop
扯得有点远,说完了finalize的缺点,下面介绍Cleaner的优势。源码分析
33 /** 34 * General-purpose phantom-reference-based cleaners. 35 * 36 * <p> Cleaners are a lightweight and more robust alternative to finalization. 37 * They are lightweight because they are not created by the VM and thus do not 38 * require a JNI upcall to be created, and because their cleanup code is 39 * invoked directly by the reference-handler thread rather than by the 40 * finalizer thread. They are more robust because they use phantom references, 41 * the weakest type of reference object, thereby avoiding the nasty ordering 42 * problems inherent to finalization. 43 * 44 * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary 45 * cleanup code. Some time after the GC detects that a cleaner's referent has 46 * become phantom-reachable, the reference-handler thread will run the cleaner. 47 * Cleaners may also be invoked directly; they are thread safe and ensure that 48 * they run their thunks at most once. 49 * 50 * <p> Cleaners are not a replacement for finalization. They should be used 51 * only when the cleanup code is extremely simple and straightforward. 52 * Nontrivial cleaners are inadvisable since they risk blocking the 53 * reference-handler thread and delaying further cleanup and finalization. 54 * 55 * 56 * @author Mark Reinhold 57 */
复制代码
根据源码中的注释能够知道,Cleaner是一种finalization的方式,它能够跟踪某个对象的生命周期,而且封装任意的cleanup代码。在GC释放完该对象后,reference-handler thread会运行封装的cleanup代码来完成资源释放。
因为Cleaner继承于PhantomReference(虚拟引用),相比于finalize的方式,它限定了不少能力,譬如访问跟踪对象的能力。因为这些能力的限定,因此它同时也避免了finalize的诸多缺陷。说白了,finalize的不少缺陷都是因为它太“能干”了。
- 若是Java对象很小,而持有的native对象很大,则须要显示调用
System.gc()
以提前触发GC。不然单纯依靠Java堆的增加来达到触发水位,可能要猴年马月了,而此时native对象产生的垃圾将堆积成山。
上文提到过的finalize的缺点3,在Cleaner这里依然得不到解决。主动触发GC是有缺陷的,由于开发者不知道怎么把控这个频率。频繁的话就会下降运行的性能,稀少的话就会致使native资源没法及时释放。所以,Android从N开始引入NativeAllocationRegistry类,一方面是简化Cleaner的使用方式,另外一方面是将native资源的大小计入GC触发的策略之中,这样一来,本来须要用户主动触发的GC即可以自动了。这个话题后面会专门成文介绍,在此先按下不表。
Referent对象,俗称被引用对象,也即Cleaner须要追踪的对象。Cleaner类继承于PhantomReference类,缘由在于它须要利用虚拟引用的特性:在跟踪对象回收时本身加入到ReferenceQueue中,继而能够自动完成native资源的回收。下图展现了一个PhantomReference对象加入到ReferenceQueue中的过程。
Referent对象在被强引用时,处于reachable状态,在GC阶段经过GC Root能够标记到这个对象,所以不会被回收。只有当没有任何强引用指向它时,它才会被容许回收。但容许回收和发生回收是两回事,这也致使Java中的弱引用类型被实现为3种。
PhantomReference对象的入列过程其实涉及到多个线程。并且Cleaner做为一种特殊的PhantomReference,它本身又有一套独立的入列规则。如下分开介绍。
Cleaner在ReferenceQueueDaemon线程的处理过程当中被看成一种特殊对象,所以无需开发者新建线程来轮询ReferenceQueue。可是须要注意,全部的Cleaner都会放在ReferenceQueueDaemon线程进行处理,所以要保证Cleaner.clean方法中作的事情是快速的,防止阻塞其余Cleaner的清理动做。
普通PhantomReference对象最后会加入构造时传入的ReferenceQueue中。对于这些ReferenceQueue有两种处理方式,一种是调用ReferenceQueue.poll
方法进行非阻塞的轮询,另外一种是经过调用ReferenceQueue.remove
方法进行阻塞等待。一般而言,ReferenceQueue的处理须要开发者新开线程,所以若是同时处理的ReferenceQueue过多,则也会形成线程资源的浪费。
本文分析基于Android 11(R)版本的源码,侧重于阐释ART虚拟机对PhantomReference对象的特殊处理,其中会涉及到GC的部分知识。
对于Concurrent Copying Collector而言,其GC能够粗略上分为Mark和Copy两个阶段。Mark结束后,全部被标记过的对象放到Mark Stack中,用于后续处理。
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 }
复制代码
Mark结束后,Collector会遍历Mark Stack中全部的对象,对每一个对象都执行Scan的动做。Scan中最终会对每一个Reference对象执行DelayReferenceReferent
的动做,若是Reference指向的referent未被标记,则将改Reference对象加入相应的native队列中。
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 } 复制代码
PhantomReference加入到phantom_reference_queue_后,接着会怎么处理呢?
art/runtime/gc/collector/concurrent_copying.cc
1434 void ConcurrentCopying::CopyingPhase() {
...
1645 ProcessReferences(self);
复制代码
在GC的Copy阶段,collector会执行ProcessReferences
函数。
art/runtime/gc/reference_processor.cc
153 void ReferenceProcessor::ProcessReferences(bool concurrent, ... 211 // Clear all phantom references with white referents. 212 phantom_reference_queue_.ClearWhiteReferences(&cleared_references_, collector); 复制代码
ProcesssReferences
函数中会将phantom_reference_queue_中的Reference添加到cleared_references_中。phantom_reference_queue_中只包含PhantomReference,而cleared_reference_则还包含有SoftReference和WeakReference。
在GC执行完以后,会调用CollectClearedReferences
生成处理cleared_references_的任务,紧接着经过Run
来执行它。
2671 collector->Run(gc_cause, clear_soft_references || runtime->IsZygote()); <====== 真正执行GC的地方
2672 IncrementFreedEver();
2673 RequestTrim(self);
2674 // Collect cleared references.
2675 SelfDeletingTask* clear = reference_processor_->CollectClearedReferences(self); <====== 生成处理cleared_references_的任务
2676 // Grow the heap so that we know when to perform the next GC.
2677 GrowForUtilization(collector, bytes_allocated_before_gc);
2678 LogGC(gc_cause, collector);
2679 FinishGC(self, gc_type); <============================================== 这一轮GC结束
2680 // Actually enqueue all cleared references. Do this after the GC has officially finished since
2681 // otherwise we can deadlock.
2682 clear->Run(self); <================================================== 指向刚刚生成的处理cleared_references_的任务
复制代码
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 }
复制代码
Run
里面将cleared_references_做为参数,调用java.lang.ref.ReferenceQueue.add
方法。这样一来,咱们便从native世界回到了Java世界。
libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java
261 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 }
复制代码
在没有任务到来时,ReferenceQueueDaemon线程处于挂起状态。
libcore/libart/src/main/java/java/lang/Daemons.java
211 @Override public void runInternal() {
212 while (isRunning()) {
213 Reference<?> list;
214 try {
215 synchronized (ReferenceQueue.class) {
216 while (ReferenceQueue.unenqueued == null) {
217 ReferenceQueue.class.wait(); <========== 经过调用wait将本线程挂起
218 }
219 list = ReferenceQueue.unenqueued;
220 ReferenceQueue.unenqueued = null;
221 }
222 } catch (InterruptedException e) {
223 continue;
224 } catch (OutOfMemoryError e) {
225 continue;
226 }
227 ReferenceQueue.enqueuePending(list);
228 }
229 }
复制代码
当新的任务到来时,ReferenceQueueDaemon线程从ReferenceQueue.class.wait
中醒来。对于全局ReferenceQueue中的元素,Cleaner和其余的PhantomReference处理方式不一样,下面将分别介绍。
全局的ReferenceQueue经过调用enqueuePending
将内部的元素分发出去。每一个Reference对象在构造时都传入了一个ReferenceQueue做为参数,这个参数就是分发后Reference对象最终所在的队列。
libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java
219 public static void enqueuePending(Reference<?> list) {
220 Reference<?> start = list;
221 do {
222 ReferenceQueue queue = list.queue; <========== 取出每一个Reference对象构造时传入的ReferenceQueue对象
223 if (queue == null) {
224 Reference<?> next = list.pendingNext;
225
226 // Make pendingNext a self-loop to preserve the invariant that
227 // once enqueued, pendingNext is non-null -- without leaking
228 // the object pendingNext was previously pointing to.
229 list.pendingNext = list;
230 list = next;
231 } else {
232 // To improve performance, we try to avoid repeated
233 // synchronization on the same queue by batching enqueue of
234 // consecutive references in the list that have the same
235 // queue.
236 synchronized (queue.lock) {
237 do {
238 Reference<?> next = list.pendingNext;
239
240 // Make pendingNext a self-loop to preserve the
241 // invariant that once enqueued, pendingNext is
242 // non-null -- without leaking the object pendingNext
243 // was previously pointing to.
244 list.pendingNext = list;
245 queue.enqueueLocked(list); <========= 将Reference对象从全局的ReferenceQueue中取出,加入到对象所属的ReferenceQueue中
246 list = next;
247 } while (list != start && list.queue == queue);
248 queue.lock.notifyAll();
249 }
250 }
251 } while (list != start);
252 }
复制代码
对于Cleaner对象而言,它并无真正地加入到构造时传入的ReferenceQueue中,而是直接在enqueueLocked
中获得了处理。
libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java
66 private boolean enqueueLocked(Reference<? extends T> r) {
67 // Verify the reference has not already been enqueued.
68 if (r.queueNext != null) {
69 return false;
70 }
71
72 if (r instanceof Cleaner) {
73 // If this reference is a Cleaner, then simply invoke the clean method instead
74 // of enqueueing it in the queue. Cleaners are associated with dummy queues that
75 // are never polled and objects are never enqueued on them.
76 Cleaner cl = (sun.misc.Cleaner) r;
77 cl.clean(); <============= 经过调用cl.clean()完成native资源的释放
78
79 // Update queueNext to indicate that the reference has been
80 // enqueued, but is now removed from the queue.
81 r.queueNext = sQueueNextUnenqueued;
82 return true;
83 }
84
85 if (tail == null) {
86 head = r;
87 } else {
88 tail.queueNext = r;
89 }
90 tail = r;
91 tail.queueNext = r;
92 return true;
93 }
复制代码
经过上面代码的85~92行能够知道,其余PhantomReference最终会加入对应的ReferenceQueue中,使其造成链表结构。添加完后,经过调用queue.lock.notifyAll
来唤醒相应的处理线程。
libcore/ojluni/src/main/java/java/lang/ref/ReferenceQueue.java
219 public static void enqueuePending(Reference<?> list) {
236 synchronized (queue.lock) {
237 do {
...
245 queue.enqueueLocked(list); <========= 将Reference对象从全局的ReferenceQueue中取出,加入到对象所属的ReferenceQueue中
246 list = next;
247 } while (list != start && list.queue == queue);
248 queue.lock.notifyAll();
...
252 }
复制代码
[Cleaner和其余PhantomReference对比]
类型 | Cleaner | 其余PhantomReference |
---|---|---|
是否加入到构造时传入的ReferenceQueue中 | ❌ | ✔️ |
最后的处理放在ReferenceQueueDaemon中 | ✔️ | ❌ |
最后的处理放在自定义的线程中 | ❌ | ✔️ |
NativeAllocationRegistry内部就是利用Cleaner来主动回收native资源的。它传入两个参数给Cleaner.create
,一个是须要追踪的Java对象,另外一个是CleanThunk,用来指定回收的方法。
libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java
243 try {
244 thunk = new CleanerThunk();
245 Cleaner cleaner = Cleaner.create(referent, thunk);
...
253 thunk.setNativePtr(nativePtr);
复制代码
Cleaner继承于PhantomReference,其构造方法有两种。经过115行能够得知,其最终传入的ReferenceQueue为dummyQueue,dummy的意思为假的、虚拟的,代表这个dummyQueue不会有实际的做用。这个和咱们上面3.2.1的分析是一致的。
libcore/ojluni/src/main/java/sun/misc/Cleaner.java
114 private Cleaner(Object referent, Runnable thunk) {
115 super(referent, dummyQueue); <===== PhantomReference的构造方法须要传入ReferenceQueue参数
116 this.thunk = thunk;
117 }
复制代码
CleanerThunk内部的nativePtr用于记录native对象的指针,freeFunction是Outer类NativeAllocationRegistry的实例字段,记录了native层资源释放函数的函数指针。有了这两个指针,即可以完成native资源的回收。
libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java
259 private class CleanerThunk implements Runnable {
260 private long nativePtr;
261
262 public CleanerThunk() {
263 this.nativePtr = 0;
264 }
265
266 public void run() {
267 if (nativePtr != 0) {
268 applyFreeFunction(freeFunction, nativePtr); <======== applyFreeFunction最终会调用freeFunction,而传入freeFunction的参数就是nativePtr
269 registerNativeFree(size);
270 }
271 }
272
273 public void setNativePtr(long nativePtr) {
274 this.nativePtr = nativePtr; <============== nativePtr是native对象的指针
275 }
276 }
复制代码
当ReferenceQueueDaemon轮询到Cleaner对象时,会调用它的clean
方法。能够看到,在143行调用了thunk.run
最终进入native世界的资源释放函数中。
libcore/ojluni/src/main/java/sun/misc/Cleaner.java
139 public void clean() {
140 if (!remove(this))
141 return;
142 try {
143 thunk.run(); <=================== 其内部调用资源释放函数
144 } catch (final Throwable x) {
145 AccessController.doPrivileged(new PrivilegedAction<Void>() {
146 public Void run() {
147 if (System.err != null)
148 new Error("Cleaner terminated abnormally", x)
149 .printStackTrace();
150 System.exit(1);
151 return null;
152 }});
153 }
154 }
复制代码