02 FinalReference.referent的回收时机
前言
在某本书上面曾经看到过, 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的回收时机相关推荐
- 一个列子演示java中软引用的回收时机
示例代码如下: import java.lang.ref.SoftReference;/*** 软引用比弱引用强,如果一个对象只有软引用,那么当堆空间不足时候,才会被回收* 该类用于演示软引用的这一性 ...
- 一个列子演示java中弱引用的回收时机
示例代码如下 import java.lang.ref.WeakReference;/*** * 弱引用比软引用还要弱,在系统GC时候,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象回收* 该 ...
- 28 关于 Finalizer
前言 // 呵呵 03.12加班, 是一件无聊的事情 接前面几篇 25 关于 Signal Dispatcher 26 关于 Attach Listener 27 关于 Reference Handl ...
- iOS底层原理:weak的实现原理
作者丨夜幕降临耶 链接: https://juejin.im/post/5e7a322f6fb9a07ca24f79bb 来源:掘金 在iOS开发过程中,会经常使用到一个修饰词weak,使用场景大家都 ...
- 如何成为一个区块链开发人员_成为开发人员是社会工作
如何成为一个区块链开发人员 Times have changed since the old days when an IT professional was this typical shy per ...
- JVM 上篇(12):垃圾回收相关概念
文章目录 System.gc() 的理解 案例:手动 GC 理解不可达对象的回收行为 1.调用 localvarGC1() 方法: 2.调用 localvarGC2() 方法 3.调用 localva ...
- JVM_06 垃圾回收相关概念[ 二 ]
一. System.gc()的理解 在默认情况下,通过System.gc( )或者Runtime . getRuntime( ).gc( )的调用,会显式触发Full GC,同时对老年代和新生代进行回 ...
- 垃圾回收②---相关概念
本篇目录 1.System.gc()的理解 1.1 手动GC理解不可达对象的回收行为 2.内存溢出与内存泄漏 2.1 基本介绍 2.2 内存溢出 2.3 内存泄漏(Memory Leak) 2.3.1 ...
- 详解JVM内存管理与垃圾回收机制5 - Java中的4种引用类型
在Java语言中,除了基础数据类型的变量以外,其他的都是引用类型,指向各种不同的对象.在前文我们也已经知道,Java中的引用可以是认为对指针的封装,这个指针中存储的值代表的是另外一块内存的起始地址(对 ...
最新文章
- Keepalived运行命令
- 使用DBUnit框架数据库插入特殊字符失败的查错经历
- [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)
- 并行算法设计与性能优化_MySQL高性能优化规范建议,从设计,命名,开发等一条线的建议...
- 压缩等级_魔兽世界:9.0会落实等级压缩吗,简单分析一下压缩等级的好处
- 使用DotNetOpenAuth搭建OAuth2.0授权框架——Demo代码简单说明
- 有人说过世界是丑陋的,但是我们的目的就是要找出那些好的东西,然后好好珍惜...
- 阿里巴巴100%云上双11
- 模电摸索日记之《模电基础》
- tkinter-界面化抽签小程序
- php 命格算法,八字格局中的弃命格mdash;mdash;从势格
- Java中的浮点型数据类型
- 高效团队都在用的目标管理工具——飞项
- 使用Grunt生成雪碧图
- 深度学习_深度学习基础知识_Internal Covariate Shift
- 联发科射频工程师题目_【MTK联发科技射频工程师面试】意外接到联发科人力资...-看准网...
- 语音识别、声纹识别的区别及测试
- Windows 系统文件资源管理器的命令行参数(如何降权打开程序,如何选择文件)
- 36氪发布《2021年中国电子签名行业研究报告》,法大大成行业头部代表
- 【毕业设计】基于单片机的GPS定位位置记录系统 - 物联网 嵌入式 stm32
热门文章
- Studing Git
- UE4中的玩家类UPlayer、ULocalPlayer 和 UNetConnection
- 多元线性回归及案例(Python)
- nlp-with-transformers系列-02-从头构建文本分类器
- 【电脑技巧】如何使用dxdiag查看电脑信息(Win11)
- 九大背包问题专题--有依赖的背包问题(树形Dp结合)
- POI IllegalArgumentException: Sheet index (0) is out of range (no sheets)问题解决
- spark 无法读取hive 3.x的表数据
- bzoj 1022: [SHOI2008]小约翰的游戏John(anti-nim)
- HTML5 Canvas核心技术迷你书