阿珍:“老徐,你这茶杯了泡的什么?红红的。”
老徐:“这是枸杞呀。”
阿珍:“枸杞?你最近什么干多了,这么虚!”
老徐:“怎么可能?看我这身体,不弱的好吧!”
阿珍一脸坏笑地说:“那就是软了。”
老徐的老脸一红,辩解到:“我这是养养生,我很强的,好吧。”
看着老徐的窘态,阿珍笑出来声。老徐起身刚要走,阿珍一把拽住老徐,说:“跟你开玩笑呢,问你个正事,我一直分不清Java的强引用、软引用、弱引用、虚引用,给我讲讲呗。”
老徐立刻自信满满的坐下,说:“那你可问对人了,我对这方面颇有研究。这四种引用级别由高到低依次是:强引用、软引用、弱引用、虚引用。”
强引用(StrongReference)
强引用是Java中最常见的引用方式,99.99%用的都是强引用。我们创建了一个对象,并把它赋值给某一个变量,我们就可以通过这个变量操作实际的对象了,比如:
String name = "万猫学社";
System.out.println(name);
当一个对象被一个或者多个变量强引用时,它就是处于一个可达状态,不会被垃圾回收机制回收掉。即使在内存不够的情况下,Java虚拟机宁愿抛出OutOfMemoryError异常,也不会回收这样的对象。
软引用(SoftReference)
软引用是通过SoftReference
类进行实现的,当一个对象只有软引用的时候,Java虚拟机的垃圾回收机制运行后,当内存空间足够时,它就不会被回收掉;当内存空间不够时,它就会被回收掉。比如:
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 5]);
System.out.println("垃圾回收前:" + softReference.get());
//建议Java虚拟机执行垃圾回收
System.gc();
System.out.println("内存足够时,垃圾回收后:" + softReference.get());
byte[][] bytes = new byte[10][];
for (int i = 0; i < 10; i++) {
bytes[i] = new byte[1024 * 1024 * 1];
}
System.out.println("内存不足时,垃圾回收后:" + softReference.get());
在运行时加入-Xmx15M
(设置Java堆的最大内存为15M)和-XX:+PrintGC
(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:
垃圾回收前:[B@1de0aca6
[GC (System.gc()) 9173K->6495K(15872K), 0.0033951 secs]
[Full GC (System.gc()) 6495K->6434K(15872K), 0.0149312 secs]
内存足够时,垃圾回收后:[B@1de0aca6
[GC (Allocation Failure) 9588K->9570K(15872K), 0.0013485 secs]
[Full GC (Ergonomics) 9570K->9506K(15872K), 0.0032467 secs]
[Full GC (Ergonomics) 12659K->12549K(15872K), 0.0083257 secs]
[Full GC (Ergonomics) 13573K->13573K(15872K), 0.0043525 secs]
[Full GC (Allocation Failure) 13573K->8435K(15872K), 0.0065695 secs]
内存不足时,垃圾回收后:null
可以看到,当内存空间足够时,软引用的对象不会被回收掉;当内存空间不够时,软引用的对象就会被回收掉。
弱引用(WeakReference)
弱引用是通过WeakReference
类进行实现的,弱引用和软引用很类似,但是比软引用的级别更低。当一个对象只有弱引用的时候,Java虚拟机的垃圾回收机制运行后,无论内存是否足够,它都会被回收掉。比如:
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1024 * 1024 * 5]);
System.out.println("垃圾回收前:" + weakReference.get());
//建议Java虚拟机执行垃圾回收
System.gc();
System.out.println("内存足够时,垃圾回收后:" + weakReference.get());
同样的,在运行时加入-Xmx15M
(设置Java堆的最大内存为15M)和-XX:+PrintGC
(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:
垃圾回收前:[B@1de0aca6
[GC (System.gc()) 9150K->6481K(15872K), 0.0015689 secs]
[Full GC (System.gc()) 6481K->1317K(15872K), 0.0062846 secs]
内存足够时,垃圾回收后:null
可以看到,即使在内存足够的时候,弱引用的对象也会被回收掉。
虚引用(PhantomReference)
虚引用通过PhantomReference
类进行实现的,虚引用完全类似于没有引用。如果一个对象只有一个虚引用,那么它就是和没有引用差不多。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,必须和引用队列(ReferenceQueue)一起使用。我们不能通过虚引用获取到被引用的对象,只有在该对象被回收后,该对象的虚引用会被放到和虚引用关联的引用队列中,比如:
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference<byte[]> phantomReference = new PhantomReference<>(new byte[1024 * 1024 * 5], referenceQueue);
System.out.println("垃圾回收前:" + phantomReference.get());
byte[][] bytes = new byte[10][];
for (int i = 0; i < 5; i++) {
bytes[i] = new byte[1024 * 1024 * 1];
}
System.out.println("垃圾回收后:" + referenceQueue.poll());
同样的,在运行时加入-Xmx15M
(设置Java堆的最大内存为15M)和-XX:+PrintGC
(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:
垃圾回收前:null
[GC (Allocation Failure) 9068K->6517K(15872K), 0.0019272 secs]
[GC (Allocation Failure) 9713K->9621K(15872K), 0.0015966 secs]
[Full GC (Ergonomics) 9621K->9506K(15872K), 0.0092758 secs]
垃圾回收后:java.lang.ref.PhantomReference@1de0aca6
可以看到,不能通过虚引用获取到被引用的对象,在该对象被回收后,可以从引用队列中获取对应的虚引用。
老徐看着阿珍一脸懵逼的样子说:“小朋友,你是不是有很多问号?”“信息量有点大,我得慢慢消化消化。”阿珍回答到。老徐说:“没关系,我给你简单总结一下,很方便理解和记忆。”
总结
- 强引用:Java中最常见的引用方式,即使内存不足也不会被垃圾回收。
- 软引用:当内存不足时,垃圾回收机制运行后对象被回收。
- 弱引用:无论内存是否足够,垃圾回收机制运行后对象被回收。
- 虚引用:主要用于跟踪对象被垃圾回收的状态,必须和引用队列一起使用。