引用
强引用
父类reference
T referent
ReferenceQueue<? super T> queue; 引用队列
Reference next;
- 1、如果引用对象在引用队列里面了,则next表示引用队列的下一个引用对象,如果是引用队列的最后一个元素则next=this
- 2、和queue共同编码了引用对象的状态
Reference discovered;
- pending 链表的下一个引用对象
static Reference pending
- 是一个static 字段,是pending 链表的头结点
ReferenceHandler
-
在Reference的static 语句里会启动这么一个线程
-
用system线程组作为Reference Handler的线程组
-
Reference Handler设置最高的线程优先级
-
是一个守护线程
-
功能
- 不断将pending 链表的引用对象拿出,如果pending链表为空,进入等待,等待vm将应用对象放入pending链表后,唤醒ReferenceHandler
- 拿出之后,如果引用对象是Cleaner的实例则调用clean函数,否则将应用对象放入引用队列里面
状态
-
active
- 状态,如果对象已经不可达,那么如果引用没有和一个引用队列关联,则ref的状态会改为Inactive,否则会改为Pending,然后将这个ref放到pending list中
-
pending
- 是pending list中的一个元素,等待Reference-handler thread去将它入队列,如果引用没有和一个引用队列关联则不会到这个状态
-
Enqueued
- 该引用是与之相关联的引用队列中的一个元素,当从引用队列中移除的时候,则变成Inactive
-
Inactive
- Inactive状态,不会再改变了
-
四种状态是由queue和next这两个字段共同编码的
-
active
- queue=ReferenceQueue.NULL或者等于一个引用队列
- next=null
-
pending
- queue等于与之关联的引用队列
- next=this
-
Enqueued
- queue=ReferenceQueue.ENQUEUED
- next等于它在引用队列中的下一个引用,如果它在引用队列的尾部,则next=this
-
Inactive
- queue = ReferenceQueue.NULL
- next = this
软引用
用于做缓存
弱引用
ThreadLocal中用于防止内存泄露
WeakHashMap
- 其中的Entry继承自弱引用,指向key对象,当key对象只有弱可达时,该key会被回收,这个时候,vm会将弱引用放到pending 链表中,同时通知reference handler线程处理,reference handler会将该弱引用从pending 链表中拿出来,放到引用队列里面来。当expungeStaleEntries被调用的时候,会从引用队列中取出相应的Entry对象,将它从hashmap中删掉
虚引用
不能通过get获取到它关联的对象
必须关联一个引用队列
虚引用所引用的对象,永远不会被回收,除非指向这个对象的所有虚引用都调用了clear函数,或者所有这些虚引用都不可达
- java9则不会影响对象的生命周期
java8及以前,虚引用指向的对象至少要经过2次gc才能回收
用法
-
用于释放对外内存
-
mysql 中用虚引用去做连接的保底释放
-
每次建立链接都会创建虚引用ConnectionFinalizerPhantomReference
-
Set connectionFinalizerPhantomRefs = ConcurrentHashMap.newKeySet();一个static的集合,保证所有的虚引用对象强可达
-
AbandonedConnectionCleanupThread 不断从引用队列里面把ConnectionFinalizerPhantomReference拿出来进行处理,
- 1、关闭socket
- 2、将ConnectionFinalizerPhantomReference里面的引用清空,保证MysqlConnection对象也能被回收
- 3、将ConnectionFinalizerPhantomReference从connectionFinalizerPhantomRefs删掉,保证虚引用本身也可以被gc
cleaner
继承自虚引用
构造器私有,调用工厂方法,则创建一个cleaner,并且将这个cleaner加入全局的cleaner链表,头指针是一个static字段,因此创建的cleaner是强可达的
必须的字段,引用、runnable, 引用队列
clean方法
- 1、将cleaner从链表中删除,保证该cleaner和cleaner指向的对象,都不可达,都可以在下次gc中被回收
- 2、调用runnable方法
用于释放直接内存
- 创建DirectByteBuffer对象时会创建一个Cleaner对象,Cleaner对象持有了DirectByteBuffer对象的引用。当JVM在GC时,如果发现DirectByteBuffer被地方法没被引用啦,JVM会将其对应的Cleaner加入到pending-reference链表中,同时通知ReferenceHandler线程处理,ReferenceHandler收到通知后,会调用Cleaner#clean方法,而对于DirectByteBuffer创建的Cleaner对象其clean方法内部会调用unsafe.freeMemory释放堆外内存。最终达到了DirectByteBuffer对象被GC回收其对应的堆外内存也被回收的目的
为什么要用cleaner,因为cleaner的处理在ReferenceHandler线程中,ReferenceHandler优先级很高
FinalReference
引出
- Object#finalize如果在执行的时候当前对象又被重新赋值,那下次GC就不会再执行finalize方法了,这是为什么啊
相关主件
-
FinalReference
- 一定要和一个引用队列进行关联
-
Finalizer
- Finalizer继承FinalReference
-
FinalizerThread
- 守护线程,优先级不是很高
- 线程名称为Finalizer
-
三个主件都是包可见性,因此程序员没有办法对之编程
特性
-
HotSpot实现上在创建一对象时,如果该类重写了Object#finalize方法且方法内容不为空,则会调Finalizer#register方法
-
Finalizer#register方法的调用契机
-
new一个对象的过程
- 分配空间
- 调用构造函数
-
Finalizer#register的调用可以在分配空间之后,或者构造函数返回之前
-
-XX:-RegisterFinalizersAtInit默认为true,为true则在构造函数返回前调用Finalizer#register,为false则在分配空间之后调用
-
在构造函数返回前调用Finalizer#register的实现?
- hotspot的实现是在Object这个类在做初始化的时候将构造函数里的return指令替换为_return_register_finalizer指令,处理该指令的时候调用Finalizer.register
-
Finalizer的register方法会调用构造方法,构建方法里面会创建Finalizer对象,同时将新创建的Finalizer加入链表的表头
-
gc的时候,当一个对象只有Finalizer指向它的时候,这个是有Finalizer会被放入pending 链表里面,之后会通知reference handler线程进行处理。这次gc,这个对象不会被gc掉,因为Finalizer有到这个对象的强引用,并且这个Finalizer还在链表中,因此这个对象强可达。reference handler会将Finalizer放到所有Finalizer共有的引用队列里面,FinalizerThread 会源源不断的从这个引用队列里面取出Finalizer,来进行处理runFinalizer
- 把该Finalizer从Finalizer链表中删掉
- 把next和prev都设置为this,表示该对象的finalize方法已经调用过了
- 调用对象的finalize方法
- 将Finalizer存储的引用清空clear
问题
-
为什么finalize方法只执行一次
-
两个原因
- 1、如果FinalizerThread已经对Finalizer进行处理了,那么这个时候Finalizer存储的引用清空clear了,Finalizer和对应没有关系了
- 2、Finalizer记录了finalize方法是否被调用过
-
为什么建议不要重写Object#finalize方法
- 因为重写了之后,那么这个对象什么时候回收的呢?经历一次gc,然后Finalizer执行了runFinalizer之后的下一次gc,才能被回收掉。因为Finalizer的优先级比较低,因此等到runFinalizer执行完,可能中间执行了多次gc了,因此重写Object#finalize方法的对象至少要经历两次gc才能回收对象,因此造成了内存泄露
在Reference的static 语句里会启动这么一个线程
用system线程组作为Reference Handler的线程组
Reference Handler设置最高的线程优先级
是一个守护线程
功能
- 不断将pending 链表的引用对象拿出,如果pending链表为空,进入等待,等待vm将应用对象放入pending链表后,唤醒ReferenceHandler
- 拿出之后,如果引用对象是Cleaner的实例则调用clean函数,否则将应用对象放入引用队列里面
active
- 状态,如果对象已经不可达,那么如果引用没有和一个引用队列关联,则ref的状态会改为Inactive,否则会改为Pending,然后将这个ref放到pending list中
pending
- 是pending list中的一个元素,等待Reference-handler thread去将它入队列,如果引用没有和一个引用队列关联则不会到这个状态
Enqueued
- 该引用是与之相关联的引用队列中的一个元素,当从引用队列中移除的时候,则变成Inactive
Inactive
- Inactive状态,不会再改变了
四种状态是由queue和next这两个字段共同编码的
-
active
- queue=ReferenceQueue.NULL或者等于一个引用队列
- next=null
-
pending
- queue等于与之关联的引用队列
- next=this
-
Enqueued
- queue=ReferenceQueue.ENQUEUED
- next等于它在引用队列中的下一个引用,如果它在引用队列的尾部,则next=this
-
Inactive
- queue = ReferenceQueue.NULL
- next = this
用于释放对外内存
mysql 中用虚引用去做连接的保底释放
-
每次建立链接都会创建虚引用ConnectionFinalizerPhantomReference
-
Set connectionFinalizerPhantomRefs = ConcurrentHashMap.newKeySet();一个static的集合,保证所有的虚引用对象强可达
-
AbandonedConnectionCleanupThread 不断从引用队列里面把ConnectionFinalizerPhantomReference拿出来进行处理,
- 1、关闭socket
- 2、将ConnectionFinalizerPhantomReference里面的引用清空,保证MysqlConnection对象也能被回收
- 3、将ConnectionFinalizerPhantomReference从connectionFinalizerPhantomRefs删掉,保证虚引用本身也可以被gc
FinalReference
- 一定要和一个引用队列进行关联
Finalizer
- Finalizer继承FinalReference
FinalizerThread
- 守护线程,优先级不是很高
- 线程名称为Finalizer
三个主件都是包可见性,因此程序员没有办法对之编程
HotSpot实现上在创建一对象时,如果该类重写了Object#finalize方法且方法内容不为空,则会调Finalizer#register方法
-
Finalizer#register方法的调用契机
-
new一个对象的过程
- 分配空间
- 调用构造函数
-
Finalizer#register的调用可以在分配空间之后,或者构造函数返回之前
-
-XX:-RegisterFinalizersAtInit默认为true,为true则在构造函数返回前调用Finalizer#register,为false则在分配空间之后调用
-
在构造函数返回前调用Finalizer#register的实现?
- hotspot的实现是在Object这个类在做初始化的时候将构造函数里的return指令替换为_return_register_finalizer指令,处理该指令的时候调用Finalizer.register
-
-
Finalizer的register方法会调用构造方法,构建方法里面会创建Finalizer对象,同时将新创建的Finalizer加入链表的表头
gc的时候,当一个对象只有Finalizer指向它的时候,这个是有Finalizer会被放入pending 链表里面,之后会通知reference handler线程进行处理。这次gc,这个对象不会被gc掉,因为Finalizer有到这个对象的强引用,并且这个Finalizer还在链表中,因此这个对象强可达。reference handler会将Finalizer放到所有Finalizer共有的引用队列里面,FinalizerThread 会源源不断的从这个引用队列里面取出Finalizer,来进行处理runFinalizer
- 把该Finalizer从Finalizer链表中删掉
- 把next和prev都设置为this,表示该对象的finalize方法已经调用过了
- 调用对象的finalize方法
- 将Finalizer存储的引用清空clear
为什么finalize方法只执行一次
-
两个原因
- 1、如果FinalizerThread已经对Finalizer进行处理了,那么这个时候Finalizer存储的引用清空clear了,Finalizer和对应没有关系了
- 2、Finalizer记录了finalize方法是否被调用过
为什么建议不要重写Object#finalize方法
- 因为重写了之后,那么这个对象什么时候回收的呢?经历一次gc,然后Finalizer执行了runFinalizer之后的下一次gc,才能被回收掉。因为Finalizer的优先级比较低,因此等到runFinalizer执行完,可能中间执行了多次gc了,因此重写Object#finalize方法的对象至少要经历两次gc才能回收对象,因此造成了内存泄露