Java引用类型有强、软、弱、虚引用。分别有不同的作用。
强引用
基本日常开发过程中GC Root能够追溯到的都是强引用。比如局部声明的局部变量。触发GC时触发GC Root不可达,否则不会收集强引用的对象。
软引用
Java中声明一个占用2MB内存的软引用。
SoftReference<byte[]> softReference = new SoftReference<>(new byte[(1024*1024*2)]);
软引用回收的条件(或关系):
- GC Root不可达。
- 当发生GC内存还是不足时,会回收软引用的对象。
示例代码:
//4mb
SoftReference<byte[]> softReference = new SoftReference<>(new byte[(1024*1024*2)]);
@Test
public void softReference() {
//-Xmx5m
System.out.println("软引用"+softReference.get());
List<byte[]> a = new ArrayList<>();
// 分配2mb,由于堆空间不足,会进行Full GC清理,清理完成后还是发现不足,会清理软引用,还是不足会抛出OOM
// 此处会清理软引用
for (int i = 0; i<=1; i++) {
a.add(new byte[1024*1024]);// 1mb
}
System.out.println("软引用end "+softReference.get());
}
软引用比较适合缓存场景,使用软引用可以无需考虑内存不足情况下的淘汰策略,JVM GC时会自动清理,比较方便。使用时需要注意,所有的软引用都会被清理,JVM清理一个软引用后,不会根据内存是否足够再次判断是否清理,而是直接清理所有软引用。
虚引用
虚引用可以用来监听对象是否已经被回收,可以替代java.lang.Object#finalize方法。我们知道java.lang.Object#finalize方法的执行是在判断一个对象是垃圾之后,在清楚对象之前执行,单线程执行。所以java.lang.Object#finalize的使用会延后内存空间的释放。而使用虚引用是在对象内存清除后触发,可以自己控制线程。
示例代码:
@Test
public void phantomReference() throws InterruptedException {
System.out.println("虚引用");
ReferenceQueue<Object> rq = new ReferenceQueue<>();
Object o1 = new Object();
PhantomReference<Object> objectPhantomReference1 = new PhantomReference<>(o1, rq);
System.out.println(objectPhantomReference1);
o1 = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(rq.poll());// 返回的是虚引用对象 不是 o1
System.out.println("幻象引用end");
}
虚引用主要使用在监听对象是否被清除进行资源释放的场景中。