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

​“365篇原创计划”第十篇。

今天呢!灯塔君跟大家讲:

深入分析Object类finalize()方法的实现原理

finalize

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

publicclassFinalizeCase{

privatestaticBlock holder =null;

publicstaticvoidmain(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 =newbyte[200*1024*1024];

@Override

protectedvoidfinalize()throwsThrowable{

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

}

}

执行完成gc日志如下:

 

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

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

finalize实现原理

 

对象的初始化过程会对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线程执行后续逻辑,实现如下:

 

pending字段什么时候会被设置?

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

 

Finalizer导致的内存泄漏

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

 

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

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

JVM源码分析之深入分析Object类finalize()方法的实现原理的更多相关文章

  1. JVM源码分析之Object.wait/notify实现

    ​ “365篇原创计划”第十一篇.   今天呢!灯塔君跟大家讲:   JVM源码分析之Object.wait/notify实现       最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提 ...

  2. JVM源码分析之堆外内存完全解读

    JVM源码分析之堆外内存完全解读   寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...

  3. JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)

    概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...

  4. JVM源码分析-类加载场景实例分析

    A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...

  5. JVM源码分析之synchronized实现

    “365篇原创计划”第十二篇.   今天呢!灯塔君跟大家讲:   JVM源码分析之synchronized实现     java内部锁synchronized的出现,为多线程的并发执行提供了一个稳定的 ...

  6. JVM源码分析之SystemGC完全解读

    JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...

  7. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  8. JVM源码分析之JVM启动流程

      原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十四篇. 今天呢!灯塔君跟大家讲: JVM源码分析之JVM启动流程 前言: 执行Java类的main方法,程序就能运 ...

  9. JVM源码分析之Java对象头实现

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...

随机推荐

  1. Java 实现 蓝桥杯 生兔子问题

    生兔子问题 有一对兔子,从出生后第四个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子.假如兔子都不死,计算第十个月兔子的总数? 分析: 四个月开始生兔子,则:F(N) = f(n-1) ...

  2. Java实现 LeetCode 5 最长回文子串

    5. 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例 1: 输入: "babad" 输出: "bab&quo ...

  3. Java实现 洛谷 P1047 校门外的树

    import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = ...

  4. JDBC连接泄露问题的排查过程总结

    当前使用的Spring JDBC版本是5.0.0.RC1,HikariCP版本是3.1.0. 今天测试同学反馈在前端页面点击次数多了,就报500错误,数据显示不出来.于是我在后台服务日志中观察发现Hi ...

  5. Java I/O模型及其底层原理

    Java I/O是Java基础之一,在面试中也比较常见,在这里我们尝试通过这篇文章阐述Java I/O的基础概念,帮助大家更好的理解Java I/O. 在刚开始学习Java I/O时,我很迷惑,因为网 ...

  6. 使用navicat连接mysql连接错误:Lost connection to Mysql server at 'waiting for initial communication packet'

    使用navicat时,报错截图如下: 原因分析: mysql开启了DNS的反向解析功能,这样mysql对连接的客户端会进行DNS主机名查找. mysql处理客户端解析过程: 当mysql的client ...

  7. (五)pom文件详解

    <?xml version="1.0" encoding="UTF-8"?> <!--是所有pom.xml的根元素,并且在里面定义了命名空间和 ...

  8. gradle的搭建和配置

    下载 官方网站:https://gradle.org/install/#manually 提供了两种下载方式,Binary-only是只下载二进制源码,Complete, with docs and ...

  9. 几种颜色模型(颜色空间):HSV CMYK RGB

    RGB和CMY颜色模型都是面向硬件的,而HSV(Hue Saturation Value)颜色模型是面向用户的. HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. ...

  10. Dubbo——服务目录

    引言 前面几篇文章分析了Dubbo的核心工作原理,本篇将对之前涉及到但却未细讲的服务目录进行深入分析,在开始之前先结合前面的文章思考下什么是服务目录?它的作用是什么? 正文 概念及作用 清楚Dubbo ...