我是靠谱客的博主 执着含羞草,这篇文章主要介绍JAVA中的引用类型,强引用软引用弱引用虚拟引用,现在分享给大家,希望可以做个参考。

文章目录

    • 强引用
    • 软引用
    • 弱引用
    • 虚拟引用
    • 引用队列
      • ReferenceQueue
      • Cleaner类

强引用

两种都属于强引用,强引用在开发中最常用,只要存在对对象的引用就不会被GC回收

复制代码
1
2
3
// 我们自己定义的实体通过直接new的形式都是强引用 Object obj = new Object();

软引用

一般对象不会被回收,当应用内存将要被耗尽的时候–>即将要发生OOM时,垃圾处理器就会把它回收,可以防止因OOM导致应用宕机不可用。

复制代码
1
2
SoftReference<String> ref = new SoftReference<String>("aaa");

弱引用

当根引用失效时,下次触发GC时会被回收,回收完即便根引用重新赋值也不会重新建立引用

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

复制代码
1
2
3
4
5
6
7
8
9
10
WeakReference<String> ref = new WeakReference<String>("aaa"); 示例 String str=new String("333"); WeakReference<String>ref=newWeakReference<>(str); System.out.println(ref.get()); str=null; System.out.println(ref.get()); System.gc(); // 手动触发一次gc System.out.println(ref.get());

结果: 333 333 null

虚拟引用

虚拟引用非真实引用,不影响正常垃圾回收
当垃圾回收器决定对PhantomReference对象进行回收时,会将其插入ReferenceQueue中。

PhantomReference.class

复制代码
1
2
3
4
5
6
7
8
9
public class PhantomReference<T> extends Reference<T> { public T get() { return null; } public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }

对一个对象而言,这个引用形同虚设,有和没有一样。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程,在对象被收集器回收时收到一个系统通知。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。

虚拟引用通常用于与引用队列ReferenceQueue同时使用

引用队列

ReferenceQueue

ReferenceQueue可以与SoftReference/WeakReference/PhantomReference配合使用

refrenceQueue本质上是一个链表,使用synchronized保证线程安全性,维护了volatile修饰的head对象,表示当前头部对象,并且维护了当前队列数量

看一下线程安全问题是如何保证的

复制代码
1
2
3
static private class Lock { }; private Lock lock = new Lock();

可以看到此处使用了一个全局锁对象,这是个空对象,只单纯作为锁对象存在,其核心只是锁住了remove方法和enqueue方法(加入队列和移除队列)

复制代码
1
2
3
4
synchronized(lock){ // ... }

当我们调用remove方法时会调用Object.wait方法阻塞,触发gc时enqueue会调用notifyAll通知线程,并非cas操作,因此对性能影响并不大

下面模拟一个场景,当我们对象被gc回收时,则移除map中的引用key

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test { private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>(); private static int _1M = 1024 * 1024; public static void main(String[] args) throws InterruptedException { final Map<Object, MybObject> map = new HashMap<>(); Thread thread = new Thread(() -> { try { int n = 0; MybObject k; while (null != (k = (MybObject) referenceQueue.remove())) { // 每次触发gc都会进来一次 System.out.println("gc:" + (++n)); map.remove(k.key); } } catch (InterruptedException e) { e.printStackTrace(); } }); thread.setDaemon(true); thread.start(); // 创建1000个对象放入map中 for (int i = 0; i < 1000; i++) { byte[] bytesKey = new byte[_1M]; byte[] bytesValue = new byte[_1M]; map.put(bytesKey, new MybObject(bytesKey, bytesValue, referenceQueue)); } Thread.currentThread().join(); } static class MybObject extends PhantomReference<byte[]> { private Object key; MybObject(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) { super(referent, q); this.key = key; } } }

运行结果

复制代码
1
2
3
4
5
6
7
gc:1 gc:2 ... gc:792 gc:793 gc:794

可以看到这里回收到794就没有回收了,剩下206个对象还在堆内存中没有被释放

注: 该结果并非在所有电脑下都一样,和电脑,配置,jvm参数都有关,每次执行结果也不尽相同

Cleaner类

通过下图可以看到Cleaner继承PhantomRefrence虚拟引用类,其本质上也还是一个Refrence。

image-20220424103149881

ReferenceHandler会不停的从pending链表中取出引用对象,这个引用对象类型可能是任意一种,当引用对象为Cleaner时,直接调用Cleaner的clean方法就结束了。

使用方式也很简单,传入对象实例和runnable线程,当对象被回收时则会触发runnable的run方法

复制代码
1
2
3
4
5
6
Cleaner.create(this, new Runnable() { @Override public void run() { } });

开发中大部分情况下用的都是强引用,某些特殊的业务场景下,开发模式下也会用到其他引用类型,建议一定要仔细阅读本文,酌情使用

最后

以上就是执着含羞草最近收集整理的关于JAVA中的引用类型,强引用软引用弱引用虚拟引用的全部内容,更多相关JAVA中内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(82)

评论列表共有 0 条评论

立即
投稿
返回
顶部