深入理解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. 引用计数算法 引用计数法 ...
随机推荐
- 计算机图形学(第四版) PDF 中文版
目录 图书介绍 下载地址 图书介绍 <计算机图形学(第四版)>是2014年电子工业出版社出版的图书,作者是Donald Hearn.M. Pauline Baker.Warren R. C ...
- [big data] main entry for Spark, Zeppelin, Delta Lake ...
1. 环境搭建 big data env setup 2. Spark 学习 spark 怎么读写 elasticsearch spark 怎么 连接 读写 ElasticSearch Spark 上 ...
- 终于有人把Modbus讲明白了
大家好!我是付工. 2012年开始接触Modbus协议,至今已经有10多年了,从开始的懵懂,到后来的顿悟,再到现在的开悟,它始终岿然不动,变化的是我对它的认知和理解. 今天跟大家聊聊关于Modbus协 ...
- DOM – IntersectionObserver
介绍 IntersectionObserver 的作用是监听某个元素是否出现在框内 (比如 viewport). 它可以实现 lazy load image, 一开始图片是没有加载的, 当图片出现在 ...
- Google 应用出海计划 | 指南针 第六期强势回归启动报名
- Win10 LTSC 从 2019(1809) 升级到 2021(21H2) 后找回丢失的 WSL
Win 10 LTST 2019 升级 2021 很简单,直接挂载 ISO 镜像以后,运行 setup.exe,剩下的就是耐心等待了. 升级完成后,用户信息和安装的软件基本上都在,VM15 启动的时候 ...
- CF1659 Codeforces Round #782 (Div. 2) 题解
之前说过的题解,E应该不会补了(大概) A Red Versus Blue 题意非常简单,构造题.给定\(r\)个红色气球和\(b\)个蓝色气球,将它们排成一排,要求使得连续出现的最多的同色气球最少, ...
- WebAssembly C++开发环境搭建
WebAssembly 开发环境搭建 简介 WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸 ...
- Nacos2.3.2在ubuntu中的部署
Nacos2.3.2 在ubuntu下的部署 下载地址 发布历史 | Nacos 官网 https://download.nacos.io/nacos-server/nacos-server-2.3. ...
- .NET使用Moq开源模拟库简化单元测试
前言 今天大姚给大家推荐一个.NET开源.流行.使用简单的.NET模拟库:Moq. Moq类库介绍 Moq是一个.NET开源.流行.使用简单的 .NET 模拟库,充分利用了.NET 的 Linq 表达 ...