java四种引用类型

前言

四种引用类型在垃圾收集方面及使用场景上的不同。

强引用

java中默认的引用方式,通过“new”关键字创建的对象,只要存在强引用,垃圾收集器就不会回收对象。

软引用

允许对象在系统内存不足时被回收。当系统内存不足时,垃圾回收器会尝试回收软引用指向的对象,但只有在 JVM 内存不足时才会真正回收。

使用场景:对于需要缓存数据,但又可以在内存不足时释放的情况,如图片缓存、数据缓存等。

1
2
3
SoftReference<Object> sr = new SoftReference<>(new Object());
// 不一定会返回object对象,有可能返回null
sr.get();

guava中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建一个 Guava Cache 实例,并配置软引用
Cache<String, String> cache = CacheBuilder.newBuilder()
.softValues() // 启用软引用
.expireAfterWrite(10, TimeUnit.MINUTES) // 缓存项写入后10分钟过期
.build();

// 向缓存中添加条目
cache.put("key1", "value1");
cache.put("key2", "value2");

// 从缓存中获取条目
String value1 = cache.getIfPresent("key1");
String value2 = cache.getIfPresent("key2");

System.out.println("Value 1: " + value1);
System.out.println("Value 2: " + value2);

// 关闭缓存
cache.invalidateAll();

其中softValues方法源码:

1
2
3
public CacheBuilder<K, V> softValues() {
return setValueStrength(Strength.SOFT);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SOFT {
@Override
<K, V> ValueReference<K, V> referenceValue(
Segment<K, V> segment, ReferenceEntry<K, V> entry, V value, int weight) {
return (weight == 1)
? new SoftValueReference<K, V>(segment.valueReferenceQueue, value, entry)
: new WeightedSoftValueReference<K, V>(
segment.valueReferenceQueue, value, entry, weight);
}

@Override
Equivalence<Object> defaultEquivalence() {
return Equivalence.identity();
}
}

SOFT枚举实例中就有SoftValueReference相关的源码。

弱引用

即使系统内存充足,只要对象只被弱引用所引用,垃圾回收器就可能会回收它。

使用场景:维护一种非强制性的映射关系,如果获取时对象还在,就使用它,否则需要实例化。对于临时性的对象引用,如事件监听器、缓存中的临时数据等。

1
2
3
WeakReference<Object> wr = new WeakReference<>(new Object());
// 不一定会返回object对象,有可能返回null
wr.get();

guava也有相关的配置使用,同上。

ThreadLocal中使用:

1
2
3
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("xxx");
threadLocal.get();

其中ThreadLocalMap的entry类就是继承自WeakReference类,其中key是弱引用对象。

为什么设计为弱引用,其实是理论上的考虑,threadLocal一般用于private static修饰,threadLocal对象持有的线程比使用threadLocal的线程短时,threadlocal对象指针被回收,如果是强引用,threadlocal对象不会被回收,是弱引用,会回收。

threadlocal对象指针——指向—-> threadlocal对象 <————强引用———- entry<——-强引用—–threadlocalmap<———-thread

threadlocal对象指针没了,threadlocal不会回收。

threadlocal对象指针——指向—-> threadlocal对象 <————弱引用———- entry<——-强引用—–threadlocalmap<———-thread

threadlocal对象指针没了,threadlocal会回收。

maven引入计算对象大小包:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
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
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {
Thread threada = new Thread(() -> {
System.out.println("进入使用threadLocal的线程方法");
String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
System.out.println(" value is " + RamUsageEstimator.sizeOf(str));
threadLocal.set(str);
System.out.println(" value is " + RamUsageEstimator.sizeOf(Thread.currentThread()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(" value is " + RamUsageEstimator.sizeOf(Thread.currentThread()));
System.out.println("threadlocal对象被回收后,entry对象也被回收");
});
threada.start();
Thread.sleep(500);
threadLocal = null;
// threadlocal被垃圾回收
System.gc();
System.out.println("回收threadLocal完成");
Thread.sleep(5000);
System.out.println("主线程执行完");
}
}
1
2
3
4
5
6
7
进入使用threadLocal的线程方法
value is 648
value is 158864
回收threadLocal完成
value is 158544
threadlocal对象被回收后,entry对象也被回收
主线程执行完

虚引用

它在任何时候都可能被垃圾回收器回收,但它不会对对象的生命周期产生影响。虚引用的存在仅仅是为了在对象被回收时收到系统通知。

使用场景:用于跟踪对象被垃圾回收器回收的情况,执行一些清理或日志记录操作。

虚引用必须和引用队列(ReferenceQueue)一起使用。当对象被垃圾回收器回收时,虚引用会被放入引用队列中,以便程序可以在对象被回收时进行一些必要的清理工作。

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
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceExample {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);

obj = null; // 解除对对象的强引用,让其成为虚引用的目标

// 执行垃圾回收
System.gc();

// 等待一段时间以便垃圾回收器完成工作
Thread.sleep(1000);

// 检查引用队列,查看是否有对象被回收
if (referenceQueue.poll() != null) {
System.out.println("Phantom reference is enqueued.");
} else {
System.out.println("Phantom reference is not enqueued.");
}
}
}

通常用来做所谓的 Post-Mortem 清理机制,Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的创建和销毁。


java四种引用类型
http://hanqichuan.com/2024/04/09/java/java四种引用类型/
作者
韩启川
发布于
2024年4月9日
许可协议