深入理解ReferenceQueue GC finalize Reference
关于对象如何销毁以及finalize更详细的信息
目录
概述
1 先看一个对象finalize的顺序问题。
2 对象再生及finalize只能执行一次
3 SoftReference WeakReference
4 PhantomReference
5 ReferenceQueue
Q&A
概述
先说一些基本的东西,GC只负责对象内存相关的清理,其他资源如文件句柄,db连接需要手动清理,以防止系统资源不足崩溃。System.gc()只是建议jvm执行GC,但是到底GC执行与否是由jvm决定的。
一个正常的对象的生命周期。
当新建一个对象时,会置位该对象的一个内部标识finalizable,当某一点GC检查到该对象不可达时,就把该对象放入finalize queue(F queue),GC会在对象销毁前执行finalize方法并且清空该对象的finalizable标识。
简而言之,一个简单的对象生命周期为,Unfinalized Finalizable Finalized Reclaimed。
Reference中引用的object叫做referent。
1 先看一个对象finalize的顺序问题。
- public class A {
- B b;
- public void finalize() {
- System.out.println("method A.finalize at " + System.nanoTime());
- }
- }
- public class B {
- public void finalize() {
- System.out.println("method B.finalize at " + System.nanoTime());
- }
- }
- A a = new A();
- a.b = new B();
- a = null;
- System.gc();
按照http://java.sun.com/developer/technicalArticles/javase/finalization/
所说,对象a在finalize之前会保持b的引用,但是实验中对象a和a中的对象b的finalize方法运行时间有先有后,而且大部分时间里,a的finalize方法的执行时间是晚于b的finalize方法的。我记着java编程语言书中说是一切可以finalize的对象的finalize方法的执行顺序是不确定的。到底应该听谁的?最好的实践就是不要依赖finalize的顺序或者写一些防御代码。
【note】我仍然坚持最好的实践就是不要依赖finalize的顺序或者写一些防御代码。但是通过进一步的学习和实验,因为a有可能复活,所以在a没有决定到底复活不复活之前b是不会被回收的。控制台的顺序问题应该是多线程的问题导致的。
【note】查看了JLS后,确定了finalize是乱序执行的。
2 对象再生及finalize只能执行一次
- public class B {
- static B b;
- public void finalize() {
- System.out.println("method B.finalize");
- b = this;
- }
- }
- B b = new B();
- b = null;
- System.gc();
- B.b = null;
- System.gc();
对象b本来已经被置null,GC检查到后放入F queue,然后执行了finalize方法,但是执行finalize方法时该对象赋值给一个static变量,该对象又可达了,此之谓对象再生。
后来该static对象也被置null,然后GC,可以从结果看到finalize方法只运行了1次。为什么呢,因为第一次finalize运行过后,该对象的finalizable置为false了,所以该对象即使以后被gc运行,也不会执行finalize方法了。
很明显,对象再生是一个不好的编程实践,打乱了正常的对象生命周期。但是如果真的需要这么用的话,应该用当前对象为原型重新生成一个对象使用,这样以后这个新的对象还可以被GC运行finalize方法。
3 SoftReference WeakReference
SoftReference会尽量保持对referent的引用,直到JVM内存不够,才会回收SoftReference的referent。所以这个比较适合实现一些cache。
WeakReference不能阻止GC对referent的处理。
4 PhantomReference
幻影引用,幽灵引用,呵呵,名字挺好听的。
奇特的地方,任何时候调用get()都是返回null。那么它的用处呢,单独好像没有什么大的用处,所以要结合ReferenceQueue。
5 ReferenceQueue
ReferenceQueue WeakReference PhantomReference都有构造函数可以传入ReferenceQueue来监听GC对referent的处理。
- public class A {
- }
- ReferenceQueue queue = new ReferenceQueue();
- WeakReference ref = new WeakReference(new A(), queue);
- Assert.assertNotNull(ref.get());
- Object obj = null;
- obj = queue.poll();
- Assert.assertNull(obj);
- System.gc();
- Assert.assertNull(ref.get());
- obj = queue.poll();
- Assert.assertNotNull(obj);
分析,在GC运行时,检测到new A()生成的对象只有一个WeakReference引用着,所以决定回收它,首先clear WeakReference的referent,然后referent的状态为finalizable,同时或者一段时间后把WeakReference放入监听的ReferenceQueue中。
注意有时候最后的Assert.assertNotNull(obj);有时会失败,因为还没有来的及把WeakReference放入监听的ReferenceQueue中。
换成PhantomReference试试,
- ReferenceQueue queue = new ReferenceQueue();
- PhantomReference ref = new PhantomReference(new A(), queue);
- Assert.assertNull(ref.get());
- Object obj = null;
- obj = queue.poll();
- Assert.assertNull(obj);
- System.gc();
- Thread.sleep(10000);
- System.gc();
- Assert.assertNull(ref.get());
- obj = queue.poll();
- Assert.assertNotNull(obj);
貌似和WeakReference没有什么区别呀,别急,还是有个细微的区别的,SoftReference和WeakReference在GC对referent状态改变时,先clear SoftReference/WeakReference对referent的引用,对应的referent状态为Finalizable,只是可以放入F
queue,然后把SoftReference/WeakReference放入ReferenceQueue。
而PhantomReference当GC对referent的状态改变时,在把PhantomReference放入ReferenceQueue之前referent已经被GC处理到Reclaimed了,即该referent被销毁了。
搞了这么多,有什么用?可以使用PhantomReference更好的控制一些关于对象生命周期的事情,当WeakReference放入ReferenceQueue时,并不能保证该referent是被销毁了。别忘了对象可以在finalize方法里再生。而使用PhantomReference,当在ReferenceQueue中发现PhantomReference时,可以保证referent已经被销毁了。
- public class A {
- static A a;
- public void finalize() {
- a = this;
- }
- }
- ReferenceQueue queue = new ReferenceQueue();
- WeakReference ref = new WeakReference(new A(), queue);
- Assert.assertNotNull(ref.get());
- Object obj = null;
- obj = queue.poll();
- Assert.assertNull(obj);
- System.gc();
- Thread.sleep(10000);
- System.gc();
- Assert.assertNull(ref.get());
- obj = queue.poll();
- Assert.assertNotNull(obj);
即使new A()出来的对象再生了,在queue中还是可以看到WeakReference。
- ReferenceQueue queue = new ReferenceQueue();
- PhantomReference ref = new PhantomReference(new A(), queue);
- Assert.assertNull(ref.get());
- Object obj = null;
- obj = queue.poll();
- Assert.assertNull(obj);
- // 第一次gc
- System.gc();
- Thread.sleep(10000);
- System.gc();
- Assert.assertNull(ref.get());
- obj = queue.poll();
- Assert.assertNull(obj);
- A.a = null;
- // 第二次gc
- System.gc();
- obj = queue.poll();
- Assert.assertNotNull(obj);
第一次gc后,由于new A()的对象再生了,所以queue是空的,因为对象没有销毁。
当第二次gc后,new A()的对象销毁以后,在queue中才可以看到PhantomReference。
所以PhantomReference可以更精细的对对象生命周期进行监控。
Q&A
Q1:有这样一个问题,为什么UT会Fail?不是说对象会重生吗,到底哪里有问题?
- public class Test {
- static Test t;
- @Override
- protected void finalize() {
- System.out.println("finalize");
- t = this;
- }
- }
- public void testFinalize() {
- Test t = new Test();
- Assert.assertNotNull(t);
- t = null;
- System.gc();
- Assert.assertNull(t);
- Assert.assertNotNull(Test.t);
- }
A: 对象是会重生不错。
这里会Fail有两个可能的原因,一个是gc的行为是不确定的,没有什么会保证gc运行。呵呵,我承认,我在console上看到东西了,所以我知道gc运行了一次。
另一个问题是gc的线程和我们跑ut的线程是两个独立的线程。即使gc线程里对象重生了,很有可能是我们跑完ut之后的事情了。这里就是时序问题了。
- public void testFinalize() throws Exception {
- Test t = new Test();
- Assert.assertNotNull(t);
- t = null;
- System.gc();
- Assert.assertNull(t);
- // 有可能fail.
- Assert.assertNull(Test.t);
- // 等一下gc,让gc线程的对象重生执行完。
- Thread.sleep(5000);
- // 有可能fail.
- Assert.assertNotNull(Test.t);
- }
这个ut和上面那个大同小异。
一般情况下,code执行到这里,gc的对象重生应该还没有发生。所以我们下面的断言有很大的概论是成立的。
- // 有可能fail.
- Assert.assertNull(Test.t);
让ut的线程睡眠5秒,嗯,gc的线程有可能已经执行完对象重生了。所以下面这行有可能通过测试。
- Assert.assertNotNull(Test.t);
嗯,测试通过。但是没有人确保它每次都通过。所以我两处的注释都声明有可能fail。
这个例子很好的说明了如何在程序中用gc和重生的基本原则。
依赖gc会引入一些不确定的行为。
重生会导致不确定以及有可能的时序问题。
所以一般我们不应该使用gc和重生,但是能深入的理解这些概念又对我们编程有好处。
这两个测试如果作为一个TestSuite跑的话,情况又会有不同。因为第一个测试失败之后和第二个测试执行之间,gc执行了对象重生。如此,以下断言失败的概率会升高。
- // 有可能fail.
- Assert.assertNull(Test.t);
To luliruj and DLevin
首先谢谢你们的回复,这个帖子发了好久了,竟然还有人回复。
reclaimed的问题可以参看本帖上边的URL。
关于finalize和ReferenceQueue和关系,主贴已经解释了,luliruj给出了不同的解释。
这个地方我们可以用小程序验证一下.
- public class Tem {
- public static void main(String[] args) throws Exception {
- ReferenceQueue queue = new ReferenceQueue();
- // SoftReference ref = new SoftReference(new B(), queue);
- // WeakReference ref = new WeakReference(new B(), queue);
- PhantomReference ref = new PhantomReference(new B(), queue);
- while (true) {
- Object obj = queue.poll();
- if (obj != null) {
- System.out.println("queue.poll at " + new Date() + " " + obj);
- break;
- }
- System.gc();
- System.out.println("run once.");
- }
- Thread.sleep(100000);
- }
- }
- class B {
- @Override
- protected void finalize() throws Throwable {
- System.out.println("finalize at " + new Date());
- }
- }
在classB的finalize上打断点,然后让ref分别为SoftReference/WeakReference/PhantomReference,可以看到。
SoftReference/WeakReference都是不需要finalize执行就可以enqueue的。这个就否掉了luliruj所说的
当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)
PhantomReference必须等待finalize执行完成才可以enqueue。
这个正如主贴所说:
而PhantomReference当GC对referent的状态改变时,在把PhantomReference放入ReferenceQueue之前referent已经被GC处理到Reclaimed了,即该referent被销毁了。
深入理解ReferenceQueue GC finalize Reference的更多相关文章
- 【转】深入理解Major GC, Full GC, CMS
声明:本文转自http://blog.csdn.net/iter_zc/article/details/41825395,转载务必声明. 很多人都分不清Major GC, Full GC的概念,事实上 ...
- 深入理解Major GC, Full GC, CMS
很多人都分不清Major GC, Full GC的概念,事实上我查了下资料,也没有查到非常精确的Major GC和Full GC的概念定义.分不清这两个概念可能就会对这个问题疑惑:Full GC会引起 ...
- 理解JVM GC
理解JVM GC对于我们把控Java应用有很大的帮助.下面我从运维角度,把网上的JVM相关的资料整理如下,以加深对JVM GC的理解.如有错误的地方,请看官指正. JVM内存使用分类 JVM的内存分区 ...
- 理解CMS GC日志
本文翻译自:https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs 准备工作 JVM的GC日志的主要参数包括如下几个:-XX:+ ...
- ? 这是个很好的问题。Go 当前的 GC 显然做了一些额外的工作,但它也跟其他的工作并行执行,所以在具有备用 CPU 的系统上,Go 正在作出合理的选择。请看 https://golang.org/issue/17969 结束语(Closing notes) 通过研究 Go 垃圾收集器,我能够理解 Go GC 当前结构的背景以及它如何克服它的弱点。Go发展得非常快。如果你对 Go感兴趣,最好继
? 这是个很好的问题.Go 当前的 GC 显然做了一些额外的工作,但它也跟其他的工作并行执行,所以在具有备用 CPU 的系统上,Go 正在作出合理的选择.请看 https://golang.org/i ...
- 深入理解JDK中的Reference原理和源码实现
前提 这篇文章主要基于JDK11的源码和最近翻看的<深入理解Java虚拟机-2nd>一书的部分内容,对JDK11中的Reference(引用)做一些总结.值得注意的是,通过笔者对比一下JD ...
- 深入理解 Java —— GC 机制
1. 基础知识 1.1 什么是垃圾回收? 程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理非常重要. 垃圾回收就是对这些无效资 ...
- JVM相关 - 深入理解 System.gc()
本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...
- 理解java的finalize
基本预备相关知识 1 java的GC只负责内存相关的清理,所有其它资源的清理必须由程序员手工完成.要不然会引起资源泄露,有可能导致程序崩溃. 2 调用GC并不保证GC实际执行. 3 finalize抛 ...
- 深入理解Java GC
一.概述 GC(Carbage Collection)垃圾收集器,由JVM自动回收已死亡的对象垃圾. 这也是Java与C++等语言的主要区别之一. 二.如何确认对象已死 1. 引用计数算法 引用计数法 ...
随机推荐
- 【Docker学习系列】Docker学习1-docker安装
从本篇开始,凯哥将和大家一起学学docker.本篇是docker学习系列第一篇:安装docker. docker安装前提条件:目前,centos发行版中的内核支持Docker.Docker运行在Cen ...
- 【漏洞分析】Penpie 攻击事件:重入攻击构造奖励金额
背景信息 2024 年 9月 3日,Penpie 合约遭受重入攻击,攻击者在重入阶段向合约添加流动性来冒充奖励金额,从而获取合约内原有的奖励代币.资产损失高达 2734 万美元. 2024 年 5月, ...
- 15 Python模块
本篇是 Python 系列教程第 15 篇,更多内容敬请访问我的 Python 合集 一个模块其实就是一个文件(以.py结尾).使用模块的好处是便于维护和重用代码. 要创建一个模块,只需编写一个新的文 ...
- 手写一个Promise.all
Promise.all 特性: 1. 按顺序返回结果数组; 2. 当所有promise完成才返回; 3. 返回第一个报错的promise的信息; 直接上代码: Promise._all = funct ...
- MyBatis——案例——查询-多条件查询-动态条件查询(关键字 if where)
动态条件查询 SQL语句会随着用户的输入或外部条件的变化而变化,我们称为 动态SQL MyBatis 对动态SQL有很强大的支撑: if choose(when,otherwise) ...
- 离线安装MySQL
离线安装mysql [下载地址](MySQL :: Download MySQL Community Server) 解压后依次执行如下命令 rpm -ivh mysql-community-comm ...
- 立足信创国产化运维,打造安全可控IT运维管理系统
随着国产化信创应用试点行业的不断扩大,应用信创产品的企事业单位逐渐增多.大多数企业均面临着陌生的国产化环境与产品,其使用习惯和解决问题的方式都面临改变.北京智和信通切实立足用户需求,提供信创运维服务. ...
- 2024年9月中国数据库流行度排行榜:TiDB重回前三,GoldenDB问鼎前五
9月墨天轮数据社区的中国数据库流行度排行榜如约而至.除了冠亚两位,排名第三至第五的数据库产品均经历了位次的变动.榜单之上,稳健的老牌强者.崛起的新兴产品.以及那些在背后默默积蓄力量.准备厚积薄发的竞争 ...
- Oracle 23c 新特性实操体验优质文章汇总 | 有奖征文进行中欢迎参与
继4月3日甲骨文宣布推出免费开发者版 Oracle Database 23c后,墨天轮社区发起 "Oracle 23c 免费开发者版特性体验"有奖征文活动,邀请大家分享Oracle ...
- 为Linux安装NVIDIA显卡驱动
Linux如何安装NVIDIA驱动 我使用的是Debian系列的Kali Linux,X11,Gnome,4060 我想要安装的是NVIDIA自家的独显驱动程序 下文展示的是为Debian系列安装NV ...