深入理解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. 引用计数算法 引用计数法 ...
随机推荐
- 详解JVM 内存结构与实战调优总结
详解JVM 内存结构与实战调优总结 GC优化案例做个总结: 1在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间.我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过GC优化 ...
- 题解:AT_arc116_b [ARC116B] Products of Min-Max
在题库里面乱翻,就翻到了. 因为在这道题里面子序列不需要考虑元素顺序,所以原序列无论是什么顺序都不会影响答案. 所以先把元素按照从大到小的顺序排列,然后考虑每个元素的贡献. 在当前序列中,对于元素 \ ...
- vue 实现组件全屏展示及退出
vue 实现组件全屏展示及退出 一.组件 采用 vue-fullscreen 组件 二.实现方式 <fullscreen ref="fullscreen" @change=& ...
- BOOST库 将源码作交叉编译 书籍下载
好记性不如烂笔头,BOOST库的移植,我也记录一下. 1. BOOST库 版本1.66.0 下载地址, 附书籍C++ BOOST库开发指南: https://download.csdn.net/dow ...
- Vue3——Pinia+Pinia持久化
Pinia 安装 Pinia npm install pinia 创建一个 pinia 实例 (根 store) 并将其传递给应用 // store/index.ts // 仓库大仓库 import ...
- scala安装及配置
window 上安装 Scala 1.Scala下载网址:https://www.scala-lang.org/download/ 2.下载后,双击 msi 文件,一步步安装即可,安装过程你可以使用默 ...
- for 和双重 for 的区别?
1. 内层的循环可以看作外层循环的语句 2. 外层循环执行 1 次 ,内层循环执行全部
- python-mongodb简单封装
#!/usr/bin/python # -*- coding: UTF-8 -*- '''@auther :mr.qin @IDE:pycharm''' import pymongo from too ...
- Linux运行时动态库搜索路径优先级
Windows运行时动态库搜索路径优先级: 在Windows运行时,动态库(通常指DLL文件)的搜索路径遵循一定的优先级顺序,以确保程序能够正确地加载所需的动态库.以下是对Windows运行时动态库搜 ...
- Unreal RecastNavigation 开源项目详解
0 前言 Recastnavigation是一个游戏AI导航库,像Unity,UE引擎中都集成了这个开源项目, HALO中使用的也是这个开源库.导航最重要的就是为NPC寻路,以及其他的寻路需求. 需要 ...