引言
《Effective Java Programming Language Guide》 一书中强烈建议不要使用java的finalize()方法去做对象消亡前的清理。因为jvm调用finalize()方法的时机并不确定,容易导致Memory-Retention Issues。通俗点讲就是内存没办法及时回收。
详细的见oracle的官方说明https://www.oracle.com/technical-resources/articles/javase/finalization.html。
Memory-Retention Issues问题简述
如果类有重写finalize()方法,JVM会将该类的对象标记为finalizable,区别于普通对象被垃圾收集器判定为不可达时,会立即回收内存,finalizable的对象会经过更多的GC周期。
下图来自oracle官方。
finalizable对象回收通常经历以下过程
- 垃圾回收器检查到该对象不可达
- 垃圾回收器将该对象加入到finalization队列,对象变成可达
- 专门的Finalizer线程从finalization队列中将对象移除,并执行对象的finalize()方法,并将对象标记为finalized
- 垃圾回收器再次发现该对象不可达,而且为finalized状态的(finalize方法已执行过),因此直接回收该对象内存。
以上过程的一些个人分析:
jvm为什么不直接在回收对象前调用finalize()方法,而是使用专门的线程去执行
java每次GC需要通过可达性分析标记大量对象,回收内存后,还涉及内存整理,如果把finalize()方法的调用也放到这个过程中,GC耗时会更长,影响系统的响应时间,所以只能由另外的过程去处理。
Memory-Retention问题
从上述过程可以看到,finalizable对象至少要2个GC周期才能将对象回收掉。更有甚者,如果系统中有大量的对象是finalizable,或者有些对象finalize()方法本身就比较耗时,加上只有一个Finalizer线程,这个线程优先级并不比别的高,还会和其他线程竞争执行资源,对象在finalization队列中呆的时间更长。在这期间如果有发生GC,垃圾收集器也是无法清理这些对象的,因此这些对象还在被finalization队列强引用。所以容易产生Memory-Retention问题。
Memory-Retention问题解决方案
《Effective Java Programming Language Guide》建议使用JDK的Cleaner来做对象消亡前的清理,其基于PhantomReference,下面系统的介绍JDK的Reference。
Reference解析
java的Reference的相关子类,用于应用层与垃圾收集器有更多的交互,通俗点讲就是垃圾收集器给应用层暴露一些API,让应用对对象的回收时机有了一定的控制能力。
SoftReference
垃圾回收器会根据内存使用情况对软引用对象进行回收,当然jvm会尽可能的不回收软引用对象,至于什么情况下回收,JDK并没有明确说明。根据其特点可以看出SoftReference可以用于缓存设计缓存,这样不用自己去设计LRU等算法对缓存进行清理。
一旦垃圾收集器认为软引用对象需要被清理时,JVM会解除软引用对对象的引用(即将referent字段置为null),同时或者稍后将软引用自己放到ReferenceQueue(如果创建软引用时有传入)
JDK保证在jvm抛出OutOfMemoryError前,清掉所有的软引用对象。除此之外,并不保证软引用对象被清理的时间点,而且也不保证软引用对象清理的顺序。
WeakReference
一旦垃圾回收器检测到对象只有弱引用,会立即解除弱引用(将弱引用的referent字段置为null),同时或者稍后将弱引用放入ReferenceQueue(如果创建软引用时有传入)。
弱引用不能阻止垃圾回收器对对象的回收,因此弱引用一般用于“规范化映射(canonicalizing mappings)”,例如WeakHashMap。
对canonicalizing mappings详细说明可以阅读
https://objectcomputing.com/resources/publications/sett/june-2000-collaborating-with-the-java-memory-manager
https://wiki.c2.com/?CanonicalizedMapping
PhantomReference
虚引用在垃圾回收器确定其引用对象可以被回收后放到ReferenceQueue,这一点与SoftReference及WeakReference有所区别。对于finalizable对象,后两者在垃圾回收器将对象放入finalization队列时,就会解除引用并将引用放入到ReferenceQueue。而PhantomReference必须在finalizable对象从finalization队列移除后,并且被垃圾回收器再次检测到不可达能够真正的回收其内存时,放入到ReferenceQueue中,而且引用不会自动解除。