博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
finalize() 原理
阅读量:4186 次
发布时间:2019-05-26

本文共 2820 字,大约阅读时间需要 9 分钟。

finalize 方法的作用是:

如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。

注意:当对象没有覆盖 finalize 方法,或者 finalize 方法已经被虚拟机调用过,虚拟机将这两种情况都视为 “没有必要执行”。也就是说,finalize 方法只会被执行一次。

如果这个对象被判定为有必要执行 finalize 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动建立的,优先级为 8 的 Finalizer 线程去执行它。

注意:如果一个对象在 finalize 方法中运行缓慢,将会导致队列后的其他对象永远等待,严重时将会导致系统崩溃。

finalize 方法是对象逃脱死亡命运的最后一道关卡。稍后 GC 将对队列中的对象进行第二次规模的标记,如果对象要在 finalize 中 “拯救” 自己,只需要将自己关联到引用上即可,通常是 this。

如果这个对象关联上了引用,那么在第二次标记的时候他将被移除出 “即将回收” 的集合;如果对象这时候还没有逃脱,那基本上就是真的被回收了。

 

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

publicclassFinalizeCase{    privatestaticBlock holder =null;    public static void main(String[] args)throwsException{    holder =newBlock();    holder =null;    System.gc();    //System.in.read();}    staticclassBlock{        byte[] _200M =newbyte[200*1024*1024];    }}

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

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

staticclassBlock{    byte[] _200M = new byte[200*1024*1024];    @Override    protected void finalize()throwsThrowable{        System.out.println("invoke finalize");    }}

执行完成gc日志如下:

和之前的gc日志进行比较,发现finalize方法确实被触发了,但是Block对象还在内存中,并没有被回收,这是为什么?

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

finalize实现原理

1、对象的初始化过程

会对has_finalizer_flag和RegisterFinalizersAtInit进行判断,如果类重写了finalize方法,且方法体不为空,则调用register_finalizer函数,继续看register_finalizer函数的实现:

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

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

对象O和Finalizer类的静态变量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被设置时,会调用ReferenceQueue的enqueue方法把Finalizer对象插入到ReferenceQueue队列中,接着通过notifyAll方法唤醒FinalizerThread线程执行后续逻辑,实现如下:

2、pending字段什么时候会被设置?—— 在GC过程的引用处理阶段

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

Finalizer导致的内存泄漏

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

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

引用

链接:https://www.jianshu.com/p/9d0552032cf3

链接:https://www.jianshu.com/p/74224cb0120f

转载地址:http://xbdoi.baihongyu.com/

你可能感兴趣的文章