前言

在某本书上面曾经看到过, Hotspot VM 的 gc 是准确式GC, 我的理解就是 这一次 gc 之后 应该会把所有的 "垃圾对象" 清理掉

假设对应的 $FinalizedClazz 重写了 finalize 方法, 并且有一个 没有任何引用的实例 o, 那么 在下一次 gc 的时候应该回收掉对象 o, 但是 自从看了这部分的代码, 以及 结合一些调试工具, 调试代码, 似乎 发现实际情况 和我理解的是不一样的

那么 这种情况下 o 多久会被回收呢 ?

测试代码如下 :

package com.hx.test10;/*** FinalReferentLifeCycle** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2019-10-18 09:48*/
public class Test09FinalReferentLifeCycle {/*** lock*/static Object lock = new Object();// Test09FinalReferentLifeCycle// select s from com.hx.test02.Test09FinalReferentLifeCycle$FinalizedClazz spublic static void main(String[] args) throws Exception {FinalizedClazz obj = new FinalizedClazz("randomString");obj = null;System.gc();Thread.sleep(1000);System.gc();// `obj` is removedSystem.out.println(" end ... ");}/*** FinalizedClazz** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2019-10-18 15:58*/static class FinalizedClazz {// for debugprivate String ident;public FinalizedClazz(String ident) {this.ident = ident;}protected void finalize() {System.out.println(" do finalize, ident : " + ident);// wait on FinalizerThread ?
//            synchronized (lock) {
//                try {
//                    lock.wait();
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }}}}

接下来 我们会针对两种情况讨论, 假设 Test09FinalReferentLifeCycle$FinalizedClazz.finalize 正常的执行一些 清理的工作, 又或者是 执行一些耗时较长的阻塞的工作

还有就是, 我们会先使用 HSDB 来验证结论, 然后 再结合具体的代码调试来说明原因

测试代码均在jdk8下面编译, 以下部分代码, 截图基于 jdk8

问题的细节

1. 基于HSDB的调试 - 正常运行FinalizedClazz.finalize

1.1 首先在 第一个 System.gc 和 第二个 System.gc 打上断点, 然后 调试启动

1.2 然后 jps 找到当前进程, 复制进程号

1.3 然后 启动 HSDB, 连接到 该VM进程

1.4 然后使用 OQL 查询给定的 Test09FinalReferentLifeCycle$FinalizedClazz 的所有实例, 如下图所示

我们可以发现 给定的 vm 里面仅仅只有一个 Test09FinalReferentLifeCycle$FinalizedClazz 的实例, 那么即为 我们 main 方法里面 new 的这一个 Test09FinalReferentLifeCycle$FinalizedClazz 的实例 obj

1.5 放开断点运行到第二个System.gc, 也是第一个 System.gc 之后, 再来查询 Test09FinalReferentLifeCycle$FinalizedClazz 的所有实例, 如下图所示

我们可以发现 第一次 System.gc 之后, 这个 Test09FinalReferentLifeCycle$FinalizedClazz 的实例 obj 依然可以找到, 也就是 还没有被 gc 掉, 并且下面 输出了 obj.finalize 的相关日志, "do finalize, ident : randomString", 表示 该对象的 finalize 的阶段已经完成

1.6 继续往下看, 走到第二次 System.gc 之后, 再来查询 Test09FinalReferentLifeCycle$FinalizedClazz 的所有实例, 如下图所示

可以看到 第二次 System.gc 之后, 这个 Test09FinalReferentLifeCycle$FinalizedClazz 的实例 obj 已经找不到了, 也就是 被 gc 掉了

2. 基于HSDB的调试 - 挂起FinalizedClazz.finalize

我们直接跳到重点, 第二次 System.gc 之前的情况均是一致的

2.1 走到第二次 System.gc 之后, 再来查询 Test09FinalReferentLifeCycle$FinalizedClazz 的所有实例, 如下图所示

可以看到 第二次 System.gc 之后, 这个 Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj 依然能够被找到, 也就是 还没有被 gc 掉

2.2 那么 为什么呢 ?, 首先我们看一下 引用 obj 的地方吧

我们来看看什么对象 在引用我们的 Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj 吧, 点击 compute liveness

这里的引用来自于 Finalizer. unfinalized, Finalizer. unfinalized 会直接, 或者间接的引用 Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj 对应的 Finalizer

Finalizer 本身有 prev, next 来构成一个双向链表, Finalizer. unfinalized 组合 prev, next 关联的链表就是 注册了 finalize, 需要finalize的对象的 Finalizer 列表

这里或许会为我们找到一些方向呢 ?

3. 梳理一下流程

在 Finalizer.register 里面打一个条件断点, 条件如下, 断点上下文如下图所示

try {Field field = finalizee.getClass().getDeclaredField("ident");return field != null;
} catch(Exception e) {return false;
}
return true;

在 Test09FinalReferentLifeCycle$FinalizedClazz.finalize 里面打一个断点

3.1 当创建 Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj 的时候, vm 发现这个对象 重写了 finalize 方法, 然后 调用了 Finalizer.register, 传入 创建的对象的引用[还尚未初始化], 创建 obj 对应的 Finalizer[implements FinalReference], 然后添加到  Finalizer.unfinalized 列表

3.2 然后第一次 System.gc 发现 Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj, 只有 FinalReference.referent 引用 obj, 判断 obj 可以回收, 然后 在 process_discovered_references 的阶段 process_phase3 将 obj 复制到 存活区, 然后 将 obj 对应的 Finalizer 移动到 Reference.pending 的列表, 然后 ReferenceHandler 线程将 Reference.pending 列表的 Reference 放入各自应该放入的 ReferenceQueue[部分内容可以参考 : https://blog.csdn.net/u011039332/article/details/102635876]

3.3 对应于我们这里, FinalizerThread 从 Finalizer.queue 里面获取 Finalizer, 来处理 finialize 业务

3.4 然后第二次 System.gc, 针对 FinalizedClazz.finalize 正常或者阻塞我们分开讨论

----3.4.1 正常运行的情况, 没有其他任何引用引用 o 了, FinalReference.referent 引用 obj 的那个 FinalReference 在 FinalizedClazz.finalize 执行之前被从 Finalizer.unfinalized 列表里面移除了, 因此 第二次 System.gc 之后 obj 被回收了

----3.4.2 阻塞FinalizedClazz.finalize的情况, FinalReference.referent 引用 obj 的那个 FinalReference 在 FinalizedClazz.finalize 执行之前被从 Finalizer.unfinalized 列表里面移除了, 但是 从上图可知 至少栈帧中还有一个 finalizee["Object finalizee = this.get()"]引用 obj, 因此 第二次 System.gc 之后 obj 还没有被回收

到这里, Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj 在这样的场景下多久被回收掉, 你应该知道了吧

4. 一些扩展

以下部分代码, 截图基于 openjdk9

另外 由于本人水平有限, 理解能力有限有限, 可能也会导致一些问题的存在

那么对象怎么注册的 Finalizer 呢 ?

以如下参数 调试启动 vm

-da -dsa -Xint -XX:+UseSerialGC -XX:+TraceFinalizerRegistration -XX:-RegisterFinalizersAtInit com.hx.test02.Test09FinalReferentLifeCycle

在 instanceKlass.register_finalizer 打一个断点, 跑到我们关注的位置[i 为Test09FinalReferentLifeCycle$FinalizedClazz 的实例  obj], 截图如下

我们可以发现, 这里 ident 为 NULL, 也就是创建了对象, 还未执行构造方法 <init>, 然后 调用了 Finalizer.register, 参数为 obj 的引用

02 FinalReference.referent的回收时机相关推荐

  1. 一个列子演示java中软引用的回收时机

    示例代码如下: import java.lang.ref.SoftReference;/*** 软引用比弱引用强,如果一个对象只有软引用,那么当堆空间不足时候,才会被回收* 该类用于演示软引用的这一性 ...

  2. 一个列子演示java中弱引用的回收时机

    示例代码如下 import java.lang.ref.WeakReference;/*** * 弱引用比软引用还要弱,在系统GC时候,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象回收* 该 ...

  3. 28 关于 Finalizer

    前言 // 呵呵 03.12加班, 是一件无聊的事情 接前面几篇 25 关于 Signal Dispatcher 26 关于 Attach Listener 27 关于 Reference Handl ...

  4. iOS底层原理:weak的实现原理

    作者丨夜幕降临耶 链接: https://juejin.im/post/5e7a322f6fb9a07ca24f79bb 来源:掘金 在iOS开发过程中,会经常使用到一个修饰词weak,使用场景大家都 ...

  5. 如何成为一个区块链开发人员_成为开发人员是社会工作

    如何成为一个区块链开发人员 Times have changed since the old days when an IT professional was this typical shy per ...

  6. JVM 上篇(12):垃圾回收相关概念

    文章目录 System.gc() 的理解 案例:手动 GC 理解不可达对象的回收行为 1.调用 localvarGC1() 方法: 2.调用 localvarGC2() 方法 3.调用 localva ...

  7. JVM_06 垃圾回收相关概念[ 二 ]

    一. System.gc()的理解 在默认情况下,通过System.gc( )或者Runtime . getRuntime( ).gc( )的调用,会显式触发Full GC,同时对老年代和新生代进行回 ...

  8. 垃圾回收②---相关概念

    本篇目录 1.System.gc()的理解 1.1 手动GC理解不可达对象的回收行为 2.内存溢出与内存泄漏 2.1 基本介绍 2.2 内存溢出 2.3 内存泄漏(Memory Leak) 2.3.1 ...

  9. 详解JVM内存管理与垃圾回收机制5 - Java中的4种引用类型

    在Java语言中,除了基础数据类型的变量以外,其他的都是引用类型,指向各种不同的对象.在前文我们也已经知道,Java中的引用可以是认为对指针的封装,这个指针中存储的值代表的是另外一块内存的起始地址(对 ...

最新文章

  1. Keepalived运行命令
  2. 使用DBUnit框架数据库插入特殊字符失败的查错经历
  3. [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)
  4. 并行算法设计与性能优化_MySQL高性能优化规范建议,从设计,命名,开发等一条线的建议...
  5. 压缩等级_魔兽世界:9.0会落实等级压缩吗,简单分析一下压缩等级的好处
  6. 使用DotNetOpenAuth搭建OAuth2.0授权框架——Demo代码简单说明
  7. 有人说过世界是丑陋的,但是我们的目的就是要找出那些好的东西,然后好好珍惜...
  8. 阿里巴巴100%云上双11
  9. 模电摸索日记之《模电基础》
  10. tkinter-界面化抽签小程序
  11. php 命格算法,八字格局中的弃命格mdash;mdash;从势格
  12. Java中的浮点型数据类型
  13. 高效团队都在用的目标管理工具——飞项
  14. 使用Grunt生成雪碧图
  15. 深度学习_深度学习基础知识_Internal Covariate Shift
  16. 联发科射频工程师题目_【MTK联发科技射频工程师面试】意外接到联发科人力资...-看准网...
  17. 语音识别、声纹识别的区别及测试
  18. Windows 系统文件资源管理器的命令行参数(如何降权打开程序,如何选择文件)
  19. 36氪发布《2021年中国电子签名行业研究报告》,法大大成行业头部代表
  20. 【毕业设计】基于单片机的GPS定位位置记录系统 - 物联网 嵌入式 stm32

热门文章

  1. Studing Git
  2. UE4中的玩家类UPlayer、ULocalPlayer 和 UNetConnection
  3. 多元线性回归及案例(Python)
  4. nlp-with-transformers系列-02-从头构建文本分类器
  5. 【电脑技巧】如何使用dxdiag查看电脑信息(Win11)
  6. 九大背包问题专题--有依赖的背包问题(树形Dp结合)
  7. POI IllegalArgumentException: Sheet index (0) is out of range (no sheets)问题解决
  8. spark 无法读取hive 3.x的表数据
  9. bzoj 1022: [SHOI2008]小约翰的游戏John(anti-nim)
  10. HTML5 Canvas核心技术迷你书