Java在java.lang.ref中提供了很多reference类型,包括软引用(SoftReference), 弱引用(WeakReference)还有虚引用(PhantomReference)。JVM的垃圾回收器对不同的引用类型有不同的行为,我试着深入探索一下。
探索之前,让我先引入一个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class BigObj { private static final int _1MB = 1024*1024; private byte[] data = new byte[_1MB]; private int id; public BigObj(int id) { this.id = id; } public int getDataLength() { return data.length; } } |
非常简单,实例化BigObj大约需要分配1M左右的堆内存;有一个自己的ID;一个自己的方法。
强引用(Strong Reference)
在讲其他引用之前,要说一下强引用。其实强引用就我们一般定义的引用,如:
|
BigObj aObj = new BigObj(0); |
aObj就是强引用类型,它指向堆中的对象实例。想让JVM回收这个对象,就是去掉所有指向该对象的强引用就可以了。这也是我们大家所熟知的。
|
public class Strong { public static void main(String[] args) { BigObj aObj = new BigObj(0); System.gc(); aObj = null; System.gc(); } } |
执行它(加上-verbose:gc), 我们就可以看到,
|
[Full GC 1514K->1208K(15872K), 0.5478635 secs] [Full GC 1298K->185K(15936K), 0.0069205 secs] |
第一次GC时,还需要1M左右的内存;但在aObj=null之后,再次GC时,大约有1M的内存被回收了。
软引用(Soft Reference)
下面来看看什么是软引用,看看下面的代码。
|
import java.lang.ref.SoftReference; public class Soft { public static void main(String[] args) { BigObj aObj = new BigObj(0); SoftReference<BigObj> softObj = new SoftReference<BigObj>(aObj); aObj = null; System.out.println(softObj.get().getDataLength() / 1024 / 1024 + "MB"); } } |
和强引用比起来,可能有点不太习惯。首先构造软引用需要给一个强引用,在强引用设置成null时,softObj.get()仍可正常工作。其实你可以认为它就比强引用复杂了一点,要用get()方法取回实例对象。再看,
|
import java.lang.ref.SoftReference; public class Soft { public static void main(String[] args) { BigObj aObj = new BigObj(0); SoftReference<BigObj> softObj = new SoftReference<BigObj>(aObj); aObj = null; softObj = null; System.gc(); } } |
运行结果,
|
[Full GC 1514K->185K(15872K), 0.0085658 secs] |
把softObj置成null, GC做回收。
好像一切都和强引用差不多。但它不止这么简单,还有一个不一样的地方,在内存紧张时(快OutOfMemory时)它也会被回收,所以它比较适合做某种缓存。
让我们实验一下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.List; public class Soft { public static void main(String[] args) { BigObj aObj = new BigObj(0); SoftReference<BigObj> softObj = new SoftReference<BigObj>(aObj); aObj = null; try { List<SoftReference<BigObj>> objs = new ArrayList<SoftReference<BigObj>>(); for (int i = 0; i < 5; ++i) { objs.add(new SoftReference<BigObj>(new BigObj(i+1))); } } catch (OutOfMemoryError e) { System.out.println("OutOfMemory"); } } } |
我们有一个List,里面全是BigObj对象。执行一下(加上-Xms5m -Xmx5m 设置Heap的大小),结果是,
|
[GC 437K->185K(4992K), 0.0013430 secs] [GC 1237K->1209K(4992K), 0.0010865 secs] [GC 2233K->2233K(4992K), 0.0009963 secs] [GC 3285K->3257K(4992K), 0.0007673 secs] [Full GC 4296K->4281K(4992K), 0.0088085 secs] [Full GC 4281K->167K(5952K), 0.0064507 secs] [GC 1202K->1191K(5952K), 0.0005265 secs] |
在内存快耗尽时,JVM尝试回收SoftReferece指向的内存,虽然最后还是耗尽了。有几点要注意,首先List里的都是强类型,所以它不会被GC回收;还要注意一下,
如果没有这一行,BigObj(0)就有一个强引用指向它,因此JVM也不会回收它,这也就失去了SoftReference原有的作用。
弱引用(Weak Reference)
然后是弱引用,基本用法和Soft差不多(如get()),但它真的比Soft还要弱。真接看Code,
|
import java.lang.ref.WeakReference; public class Weak { public static void main(String[] args) { BigObj aObj = new BigObj(0); WeakReference<BigObj> weakObj = new WeakReference<BigObj>(aObj); aObj = null; System.gc(); if (weakObj.get() == null) { System.out.println("Collected!"); } } } |
运行结果,
|
[Full GC 1514K->1209K(15872K), 0.0088999 secs] Collected! |
可见,在失去所有强引用时,WeakReference也会被回收。
虚引用(PhantomReference)
首先名字很酷,为什么叫虚呢?因为它的get()方法永远返回null。看看Code,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class Phantom { public static void main(String[] args) { BigObj aObj = new BigObj(0); ReferenceQueue<BigObj> refQueue = new ReferenceQueue<BigObj>(); PhantomReference<BigObj> phantomReference = new PhantomReference<BigObj>(aObj, refQueue); if (phantomReference.get() == null) { System.out.println("Get nothing!"); } aObj = null; System.gc(); } } |
执行结果(-Xms2m -Xmx2m):
|
[GC 425K->185K(1984K), 0.0013103 secs] [Full GC 185K->185K(1984K), 0.0086238 secs] Get nothing! [Full GC 1245K->1209K(3012K), 0.0061249 secs] |
结果说明,Phantom和Soft有一样的特点,不会随着主引用不变化而发生GC,主有在内存吃紧时,才会有GC动作。
此外这里还有个新东西,就是ReferenceQueue。这是PhantomReference存在的原因。
引用队列(ReferenceQueue)
其实前面三种引用类型的构造函数都可以接收ReferenceQueue,但PhantomReference是强制需要的。如果使用了它,在堆对象释放之前,它被被放到ReferenceQueue里。这使我们能够在堆对象被回收之前采取行动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class Weak { public static void main(String[] args) { BigObj aObj = new BigObj(0); ReferenceQueue<BigObj> refQueue = new ReferenceQueue<BigObj>(); WeakReference<BigObj> weakObj = new WeakReference<BigObj>(aObj, refQueue); aObj = null; System.gc(); if (weakObj.get() == null) { System.out.println("Collected!"); } if (refQueue.poll() == weakObj) { System.out.println("In ref queue!"); } } } |
结果:
|
[Full GC 1514K->1209K(15872K), 0.0092206 secs] Collected! In ref queue! |
看了一些文章,感觉不同的JVM对于这几种Reference的支持还是不太一致的。
一些参考文献
- http://www.ibm.com/developerworks/cn/java/j-lo-langref/
- http://www.ibm.com/developerworks/cn/java/j-refs/