JVM源码分析之深刻分析Object类finalize()方法的实现原理

原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注

今天呢!灯塔君跟你们讲:java

深刻分析Object类finalize()方法的实现原理面试

若是类中重写了finalize方法,当该类对象被回收时,finalize方法有可能会被触发,下面经过一个例子说明finalize方法对垃圾回收有什么影响。数组

`public class FinalizeCase {缓存

private static Block holder = null;微信

public static void main(String[] args) throws Exception {jvm

holder = new Block();socket

holder = null;ide

System.gc();函数

//System.in.read();oop

}

static class Block {

byte[] _200M = new byte[200*1024*1024];

}

}`

Block类中声明一个占用内存200M的数组,是为了方便看出来gc以后是否回收了Block对象,执行完的gc日志以下:

从gc日志中能够看出来,执行完System.gc()以后,Block对象被如期的回收了,若是在Block类中重写了finalize方法,会是同样的结果么?

`static class Block {

byte[] _200M = new byte[200*1024*1024];

@Override

protected void finalize() throws Throwable {

System.out.println("invoke finalize");

}

}`

执行完成gc日志以下:

和以前的gc日志进行比较,发现finalize方法确实被触发了,可是Block对象还在内存中,并无被回收,这是为何?

下面对finalize方法的实现原理进行分析。

finalize实现原理

对象的初始化过程会对has_finalizer_flagRegisterFinalizersAtInit进行判断,若是类重写了finalize方法,且方法体不为空,则调用register_finalizer函数,继续看register_finalizer函数的实现:

其中Universe::finalizer_register_method()缓存的是jdkjava.lang.ref.Finalizer类的register方法,实现以下:

在jvm中经过JavaCalls::call触发register方法,将新建的对象O封装成一个Finalizer对象,并经过add方法添加到Finalizer链表头。

对象OFinalizer类的静态变量unfinalized有联系,在发生GC时,会被断定为活跃对象,所以不会被回收回收

FinalizerThread线程

Finalizer类的静态代码块中会建立一个FinalizerThread类型的守护线程,可是这个线程的优先级比较低,意味着在cpu吃紧的时候可能会抢占不到资源执行。

FinalizerThread线程负责从ReferenceQueue队列中获取Finalizer对象,若是队列中

没有元素,则经过wait方法将该线程挂起,等待被唤醒

若是返回了Finalizer对象,执行对象的runFinalizer()方法,其实能够发现:在runFinalizer()方法中主动捕获了异常,即便在执行finalize方法抛出异常时,也没有关系。

经过hasBeenFinalized方法判断该对象是否还在链表中,并将该Finalizer对象从链表中删除,这样下次gc时就能够把原对象给回收掉了,最后调用了native方法invokeFinalizeMethod,其中invokeFinalizeMethod方法最终会找到并执行对象的finalize方法。

ReferenceHandler线程

有个疑问:既然FinalizerThread线程是从ReferenceQueue队列中获取Finalizer对象,那么Finalizer对象是在什么状况下才会被插入到ReferenceQueue队列中?

Finalizer

的祖父类Reference中定义了ReferenceHandler线程,实现以下:

pending被设置时,会调用ReferenceQueueenqueue方法把Finalizer对象插入到ReferenceQueue队列中,接着经过notifyAll方法唤醒FinalizerThread线程执行后续逻辑,实现以下:

pending字段何时会被设置?

在GC过程的引用处理阶段,经过oopDesc::atomic_exchange_oop方法把发现的引用列表设置在pending字段所在的地址

Finalizer致使的内存泄漏

日常使用的Socket通讯,SocksSocketImpl的父类重写了finalize方法

这么作主要是为了确保在用户忘记手动关闭socket链接的状况下,在该对象被回收时可以自动关闭socket来释放一些资源,可是在开发过程当中,真的忘记手动调用了close方法,那么这些socket对象可能会由于FinalizeThread线程迟迟没有执行到这些对象的finalize方法,而致使一直占用某些资源,形成内存泄露。

365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板
相关文章
相关标签/搜索