JAVA FinalReference
引入
使用MAT分析dump出的内存时,常会看到java.lang.ref.Finalizer占用内存也不小,比较纳闷我们在编程中并没有用到这个东西,为什么他会出现并且占用分量不算小的一部分内存呢?1
2
3
4final class Finalizer extends FinalReference {
private static ReferenceQueue queue = new ReferenceQueue();
//... ...
}
结合它的数据结构基本可以看出来,Finalizer中持有一个一个引用队列。猜测是这个队列吃掉了那些内存。
引用类型
Java开发不必关心内存的释放、申请和垃圾回收,这些事情都有JVM代劳,但是JVM依然提供了一些方式,让我们能够在应用的层次利用内存或者GC特性,从而更好的使用内存。Reference(引用)就是其中一种。
- StrongReference(强引用)
我们平时开发中new一个对象出来,这种引用便是强引用。 JVM 系统采用 Finalizer 来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法。 - SoftReference(软引用)
当内存足够的时候,软引用所指向的对象没有其他强引用指向的话,GC的时候并不会被回收,当且只当内存不够时才会被GC回收(调用finalize方法)。强度仅次于强引用。 - WeakReference(弱引用)
弱引用指向的对象没有任何强引用指向的话,GC的时候会进行回收。 - PhantomReference(虚引用)
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
TODO引用详细介绍单独在另一篇作介绍
java.lang.ref包简介
ref包下对应了Java中对应几种引用类型,改包下的类的可见性均为包内可见。FinalReference可以看作是强引用的一个对应。
FinalReference
FinalReference由JVM来实例化,VM会对那些实现了Object中finalize()方法的类实例化一个对应的FinalReference。
注意:实现的finalize方法体必须非空。
Finalizer
Finalizer是FinalReference的子类,该类被final修饰,不可再被继承,JVM实际操作的是Finalizer。当一个类满足实例化FinalReference的条件时,JVM会调用Finalizer.register()进行注册。(PS:后续讲的Finalizer其实也是在说FinalReference。)
何时注册(实例化FinalReference)
JVM在类加载的时候会遍历当前类的所有方法,包括父类的方法,只要有一个参数为空且返回void的非空finalize方法就认为这个类在创建对象的时候需要进行注册。
对象的创建其实是被拆分成多个步骤,注册的时机可以在为对象分配好内存空间后,也可以在构造函数返回之前,这个点由-XX:-RegisterFinalizersAtInit控制,这个参数默认为true,即:在构造函数返回之前调用。注册入口是Finalizer的register()方法。
1 | final class Finalizer extends FinalReference { |
何时进入ReferenceQueue
GC工作时,如果发现对象只被Finalizer类引用,说明他可以被回收了,那么就把该对象从对象链中取出,放入ReferenceQueue,并通知FinalizerThread去消费。也就是说,本次GC并不能回收掉这个对象占用的内存。
ReferenceQueue是个典型的生产消费队列,此处不在赘述,可看其源码,实现很简单。
FinalizerThread线程
在Finalizer类的clinit方法(静态块)里,会创建一个FinalizerThread守护线程,这个线程的优先级不是最高的,这就意味着在CPU很紧张的情况下其被调度的优先级可能会受到影响。
FinalizerThread业务很简单,从ReferenceQueue拿出Finalizer,执行finalize方法,并且忽略其抛出的所有异常。执行完毕后,该对象称为真正的垃圾对象,再次发生GC,他的一生也就结束了。
1 | private static class FinalizerThread extends Thread { |
GC回收问题
对象因为Finalizer的引用而变成了一个临时的强引用,即使没有其他的强引用,还是无法立即被回收;
对象至少经历两次GC才能被回收,因为只有在FinalizerThread执行完了f对象的finalize方法的情况下才有可能被下次GC回收,而有可能期间已经经历过多次GC了,但是一直还没执行对象的finalize方法;
CPU资源比较稀缺的情况下FinalizerThread线程有可能因为优先级比较低而延迟执行对象的finalize方法;
因为对象的finalize方法迟迟没有执行,有可能会导致大部分f对象进入到old分代,此时容易引发old分代的GC,甚至Full GC,GC暂停时间明显变长,甚至导致OOM;
对象的finalize方法被调用后,这个对象其实还并没有被回收,虽然可能在不久的将来会被回收。
举个例子
1 | /** |
finalizer的生存周期
转自http://blog.2baxb.me/archives/974
- 在创建对象时,如果对象override了finalize()方法,jvm会同时创建一个Finalizer对象
- 所有Finalizer对象组成了一个双向链表
- 所有Finalizer对象都有一个名为queue的成员变量,指向的都是Finalizer类的静态Queue。
- cms gc执行到mark阶段的最后时,会把需要gc的对象加入到Reference的pending list中。
- 有一个专门的高级别线程Reference Handler处理pending list,把pending list中的对象取出来,放到这个对象所指的Reference Queue中,对于Finalizer对象来说,这个queue指向Finalizer类的静态Queue。
- Finalizer类有一个专门的线程负责从queue中取对象,并且执行finalizer引用的对象的finalize函数。
Java引用类型可参见
http://benjaminwhx.com/2016/09/10/%E8%AF%A6%E8%A7%A3java-lang-ref%E5%8C%85%E4%B8%AD%E7%9A%844%E7%A7%8D%E5%BC%95%E7%94%A8/
https://www.ibm.com/developerworks/cn/java/j-lo-langref/
http://www.importnew.com/20468.html
http://www.infoq.com/cn/articles/cf-java-garbage-references
小生不才,以上如有描述有误的地方还望各位不吝赐教 !^_^!