开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 23 天,点击查看活动详情
上一篇文章我们讲解了 jvm垃圾回收讲到了引用关系, 今天我们介绍一下最后两种引用关系 弱引用及虚引用
1.弱引用
弱引用对象指那某个对象与弱引用关联,那么当JVM在进行gc垃圾回收时,无论内存是否充足,都会回收此类对象。
- 在java中,用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短
- 只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存
- 弱引用本身(WeakReference对象)也会占用内存,它的回收需要使用引用队列。
弱引用的应用场景
- WeakHashMap WeakHashMap<String, String> map = new WeakHashMap<String, String>();
- 当key只有弱引用时,GC发现后会自动清理键和值,作为简单的缓存表解决方案。
- ThreadLocal 的ThreadLocalMap 就是弱引用
- ThreadLocal.ThreadLocalMap.Entry 继承了弱引用,key为当前线程实例,和WeakHashMap基本相同。
下面我们来测试一下弱引用,设置启动JVM参数
-verbose:gc -Xmx10M -Xms10M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
- Xmx10M 最大堆内存 10M
- Xms10M 最小堆内存 10M
1.1弱引用测试
设置JVM启动参数
创建MyFinalObj类
public class MyFinalObj {
//一般开发中不用调用这个方法
@Override
protected void finalize() throws Throwable {
System.out.println(Thread.currentThread().getName() + "\t" + "---方法调用 finalize ....");
}
}
创建测试方法
- 创建弱引用 WeakReference weakReference = new WeakReference(new MyFinalObj());
- 手动gc System.gc()
- 查看结果 log.info("-----gc after内存够用:: " + weakReference.get());
@Slf4j
public class ReferTest {
public static void main(String[] args) {
//创建弱引用
WeakReference weakReference = new WeakReference(new MyFinalObj());
log.info("-----gc before内存够用:: " + weakReference.get());
//手动GC
System.gc();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("-----gc after内存够用:: " + weakReference.get());
}
}
1.2 虚引用执行结果
- 开始创建对象
- 执行gc, 调用了 finalize方法去销毁对象
- 弱引用对象被销毁
- 最终打印日志,看到即使内存充足
- gc时候弱引用对象依旧被销毁
22:31:34.052 [main] INFO com.jzj.jvmtest.gctest.ReferTest - -----gc before内存够用:: com.jzj.jvmtest.gctest.MyFinalObj@255316f2
Finalizer ---方法调用 finalize ....
22:31:35.069 [main] INFO com.jzj.jvmtest.gctest.ReferTest - -----gc after内存够用:: null
2.虚引用
虚引用时所有引用类型中最弱的一个,一个持有弱引用的对象,和没有引用几乎是一样的
- 虚引用随时都可能被垃圾回收器回收,如果一个对象仅持有虚引用,那么它就和没有任何引用一样
- 虚引用并不会决定对象的生命周期。
- 调用虚引用的get()方法取得强引用时,一般来说总会失败
- 虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
- 当垃圾回收器准备回收一个对象时,如果发现他还有虚引用,就会在垃圾回收后销毁这个对象,将这个对象加入引用队列。
- java中使用PhantomReference来表示虚引用。
既然这么弱,为什么还要有虚引用对象 ? 他的应用场景是什么?
虚引用的主要目的是在一个对象所占的内存被实际回收之前得到通知,从而可以进行一些相关的清理工作,主要用于检测对象是否已经从内存中删除。
创建测试类, 测试虚引用
- 使用PlantomReference引用对象时,这种引用方式就是虚引用
- 虚引用需要结合引用队列ReferenceQueue使用,比如NIO中的ByteBuffer就使用到了虚引用来实现在ByteBuffer对象GC时,释放它申请的直接内存。
- 可以看到刚创建完, 打印就是null,已经被回收了
- 所以虚引用随时都可能被垃圾回收器回收
public static void main(String[] args) throws Exception {
MyFinalObj myObj = new MyFinalObj();
ReferenceQueue<MyFinalObj> objQueue = new ReferenceQueue<>();
PhantomReference<MyFinalObj> prObj = new PhantomReference<>(myObj, objQueue);
//创建出来 就被回收了
log.info(String.valueOf(prObj.get()));
Thread.sleep(1000);
log.info(String.valueOf(prObj.get()));
}
执行结果, 创建完毕,对象即被回收,为null
22:50:39.682 [main] INFO com.jzj.jvmtest.gctest.ReferTest - null
22:50:40.691 [main] INFO com.jzj.jvmtest.gctest.ReferTest - null
3.Java的 4种引用关系总结
java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用。
下面分别针对 引用类型及回收时间,应用场景做了对比如下:
| 引用类型 | 被垃圾回收时间 | 应用场景 | 生存时间 |
|---|---|---|---|
| 强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
| 软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
| 弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
| 虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
至此, Java的四种引用关系已经被我们彻底讲解完成,分别使用代码讲解了四种引用关系,下面我们开始着重开始讲解垃圾回收机制及jvm的垃圾清理机制