先说一些基本的东西,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的顺序问题
package com.bijian.study; class A { B b; public void finalize() { System.out.println("method A.finalize at " + System.nanoTime()); } } class B { public void finalize() { System.out.println("method B.finalize at " + System.nanoTime()); } } public class Test { public static void main(String[] args) { A a = new A(); a.b = new B(); a = null; System.gc(); } }
原作者的分析如下:
但我在JDK1.8上Debug控制线程的执行顺序的结果来看,只存在两种结果:
a.控制台没有任何输出,说明GC线程未来得及执行,主线程就执行结束了,而GC线程由于是守护线程所以也就自动结束了。
b.控制台输出如下内容:
method B.finalize at 2091777063532 method A.finalize at 2102345356491
我觉得GC线程回收对象时,会先执行关联对象的finalize()方法,再执行本对象的finalize()方法。[当然,这里也只是我根据运行结果的一种推测,也许正像原作者所说的finalize是乱序执行的]
2.对象再生及finalize只能执行一次
package com.bijian.study; class B { static B b; public void finalize() { System.out.println("method B.finalize"); b = this; } } public class Test { public static void main(String[] args) { B b = new B(); b = null; System.gc(); B.b = null; System.gc(); } }
运行结果如果有输出,输出结果如下:
method B.finalize
对象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的处理。
package com.bijian.study; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; class A { } public class Test { public static void main(String[] args) { ReferenceQueue queue = new ReferenceQueue(); WeakReference ref = new WeakReference(new A(), queue); System.out.println(ref.get()); Object obj = null; obj = queue.poll(); System.out.println(obj); System.gc(); System.out.println(ref.get()); obj = queue.poll(); System.out.println(obj); } }
运行结果:
com.bijian.study.A@139a55 null null java.lang.ref.WeakReference@1db9742
分析,在GC运行时,检测到new A()生成的对象只有一个WeakReference引用着,所以决定回收它,首先clear WeakReference的referent,然后referent的状态为finalizable,同时或者一段时间后把WeakReference放入监听的ReferenceQueue中。
注意有时候最后的System.out.println(obj);有时会失败,因为还没有来的及把WeakReference放入监听的ReferenceQueue中。
换成PhantomReference试试:
package com.bijian.study; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; class A { } public class Test { public static void main(String[] args) throws InterruptedException { ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference(new A(), queue); System.out.println(ref.get()); Object obj = null; obj = queue.poll(); System.out.println(obj); System.gc(); Thread.sleep(10000); System.gc(); System.out.println(ref.get()); obj = queue.poll(); System.out.println(obj); } }
运行结果:
null null null java.lang.ref.PhantomReference@139a55
貌似和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已经被销毁了。
package com.bijian.study; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; class A { static A a; public void finalize() { a = this; } } public class Test { public static void main(String[] args) throws InterruptedException { ReferenceQueue queue = new ReferenceQueue(); WeakReference ref = new WeakReference(new A(), queue); System.out.println(ref.get()); Object obj = null; obj = queue.poll(); System.out.println(obj); System.gc(); Thread.sleep(10000); System.gc(); System.out.println(ref.get()); obj = queue.poll(); System.out.println(obj); } }
运行结果:
com.bijian.study.A@139a55 null null java.lang.ref.WeakReference@1db9742
即使new A()出来的对象再生了,在queue中还是可以看到WeakReference。
将上面实例改成PhantomReference如下:
package com.bijian.study; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; class A { static A a; public void finalize() { a = this; } } public class Test { public static void main(String[] args) throws InterruptedException { ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference(new A(), queue); System.out.println(ref.get()); Object obj = null; obj = queue.poll(); System.out.println(obj); // 第一次gc System.gc(); Thread.sleep(10000); System.gc(); System.out.println(ref.get()); obj = queue.poll(); System.out.println(obj); A.a = null; // 第二次gc System.gc(); obj = queue.poll(); System.out.println(obj); } }
运行结果:
null null null null java.lang.ref.PhantomReference@6d8acf
第一次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之后的事情了。这里就是时序问题了。
相关推荐
System.out.println("obj [Date: " + this.getTime() + "] is gc"); } public String toString() { return "Date: " + this.getTime(); } } ``` 在这个类中,我们对java.util.Date类进行了扩展,并重写了...
Java Reference源码解析 Java Reference是Java语言的一种机制,用于追踪对象的可达性和垃圾...通过了解Java Reference的源码,我们可以更好地理解Java的垃圾回收机制,并且能够更好地应用Java Reference在实际开发中。
ReferenceQueue<Bean> queue = new ReferenceQueue(); PhantomReference<Bean> phantom = new PhantomReference(new Bean("name", 10), queue); System.gc(); // 强制执行垃圾回收 System.out.println(phantom.get()...
本文将深入探讨Java中的四种引用类型:强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)以及幽灵引用(PhantomReference),并讨论它们在GC工作原理和性能调优中的作用。 1. 强引用...
在Java编程语言中,引用是理解内存管理的关键概念。这个"referenceInJava"项目专注于探讨四种不同类型的引用:强引用、软...通过深入理解这些引用类型,开发者可以更好地优化内存使用,提高应用程序的性能和稳定性。