文章内容,参考《深入理解java虚拟机》和
https://segmentfault.com/a/1190000037439801
。
强引用
对象中普遍存在的引用赋值:Object object = new Object() 这种。只要强引用关系还存在,那么垃圾回收期就永远不会回收,一般需要通过设置 null 值来进行回收。比如:object = null
软引用
对应的 java 类是 java.lang.ref.SoftReference 。当对象只有软引用可用(可到达)的时候,在垃圾回收后,内存不足的时候回再次发出垃圾回收,回收该对象,也可以配合引用队列来释放软引用本身
比如代码如下:
Object object = new Object();
// softRef 为软引用
// 另一个构造方法 SoftRenference(T referent, ReferenceQueue<? super T> q), 可传入引用队列来回收引用自身所占用的内存
SoftReference<Object> softRef = new SoftReference<>(object);
// 如果内存足够,get() 方法调用就会返回 object 对象,如果内存不足,GC 时会回收 softRef 所关联的对象 object ,此时 get() 调用就会返回 null
softRef.get();
软引用在实际中的应用,一般是为了避免内存溢出的发生。加入有一批图片资源,需要从磁盘中读取并缓存到内存中方便下次读取的话,而这部分的缓存并不是必须的,你不希望这部分缓存太大而导致内存溢出,那么你就可以考虑使用软引用。如下代码,堆内存只有20M ,需要读取5张 4M 的图片存入一个 list 中,分别演示强引用
和软引用
:
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用
* 配置jvm启动参数: -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class GcDemo01 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) throws IOException {
//strong();
soft();
}
/**
* 使用强引用导致堆内存溢出 java.lang.OutOfMemoryError: Java heap space
*/
public static void strong() {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new byte[_4MB]);
}
}
/**
* 使用软引用,当内存不足垃圾回收时list中个别软引用对象所引用的byte[]对象会被回收,所以ref.get()可能返回null
*/
public static void soft() {
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> softRef = new SoftReference<>(new byte[_4MB]);
System.out.println(softRef.get());
list.add(softRef);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
弱引用
弱引用对应的 java 类是 java.lang.WeakReference<T>
,弱引用于软引用基本相同,区别是:仅有弱引用可达到对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用指向的对象。
虚引用
虚引用对应的 java 类是 java.lang.PhantomReference<T>
,与软引用和弱引用不同的是,虚引用必须配合引用队列一起使用,因此只有一个构造方法 PhantomReference(T referent, ReferenceQueue<? super T> q)
,且虚引用的get()方法始终返回 null。
虚引用实际上并不是来指向 java 对象的,所以 get() 方法始终返回 null,虚引用的作用主要体现在 释放直接内存
。我们在使用 ByteBuffer.allocateDirect()
申请分配直接内存时,会创建一个虚引用(Cleaner) 并且指向 ByteBuffer 对象,当 ByteBuffer 对象被回收的时候,虚引用(Cleaner) 会进入引用队列中,然后调用 Clean#clean() 方法来释放直接内存。
终结器引用-finalize()
终结器引用的 java 类是 java.lang.FinalReference<T>
,也必须配合引用队列一起使用,终结器引用主要作用是在对象被真真回收之前,用来调用对象的finalize() 方法的。
当一个对象重写了 finalize() 方法,在垃圾回收的时候,终结器引用进入引用队列 ( 被引用对象暂时还没有被回收),再由 Finalizer 线程通过终结器引用找到了被引用对象并调用它的 finalize() 方法,第二次 GC 时才能回收被引用对象。
参考《深入理解java虚拟机第三版》的3.2.4可以知道,对象如果被可达性分析算法标记了过后,并不是马上就会被回收,首先虚拟机在可达性分析算法过程中,会对与 GC Roots 没有连接的对象进行第一次标记,然后等待第二次标记,如果在第二次标记的时候,还是没有与 GC Roots根对象进行连接,就会进行第二次标记,然后就肯定被回收了。所以,如果需要对象不被马上回收,可以重写 finalize() 方法。