Java中的五种引用方式底层详解

分类: 注册送365元可提款 时间: 2025-11-20 08:14:31 作者: admin 阅读: 2608 点赞: 80
Java中的五种引用方式底层详解

在GC算法的可达性算法中描述的对象引用,一般指的是强引用,即是GCRoot对象对普通对象有引用关系,只要这层关系存在,普通对象就不会被回收,而在Java中一共有五种引用关系。

目录

强引用 (Strong Reference)

软引用 (Soft Reference)

使用软引用实现简单缓存

一个实际应用示例:图片缓存

带有引用队列的增强版缓存

使用场景和注意事项:

弱引用 (Weak Reference)

虚引用 (Phantom Reference)

终结器引用 (Final Reference)

总结:

软引用和虚引用可以单独使用,也可以使用引用队列

对比使用和不使用引用队列的区别

虚引用必须使用引用队列

关键细节

引用对象的生命周期:

强引用 (Strong Reference)

最常见的引用类型,只要强引用存在,垃圾收集器就永远不会回收被引用的对象。

public class StrongReferenceDemo {

public static void main(String[] args) {

// 创建强引用

Object strongRef = new Object();

// 即使手动触发垃圾回收,strongRef指向的对象也不会被回收

System.gc();

// 只有当strongRef = null 后,对象才可能被回收

strongRef = null;

System.gc();

}

}

软引用 (Soft Reference)

内存充足时不会被回收,内存不足时会被回收。常用于实现内存敏感的缓存,要先通过参数给jvm设置一定的内存空间,内存空间如果设置太大,即使是如图中分配了1000M以后并不会造成内存不足,也就不会回收软引用引用的对象。

public class SoftReferenceDetail {

public static void main(String[] args) {

// 创建一个大对象

byte[] data = new byte[1024 * 1024 * 100]; // 100MB

// 创建软引用

SoftReference softRef = new SoftReference<>(data);

// 清除强引用

data = null;

// 检查软引用是否被回收

System.out.println("软引用对象是否存在:" + (softRef.get() != null));

try {

// 分配大量内存,触发GC

byte[] newData = new byte[1024 * 1024 * 1000]; // 1000MB

} catch (OutOfMemoryError e) {

// 检查软引用是否被回收

System.out.println("软引用对象是否存在:" + (softRef.get() != null));

}

}

}

使用软引用实现简单缓存

public class SoftReferenceCache {

// 使用ConcurrentHashMap存储缓存项

private final ConcurrentHashMap> cache = new ConcurrentHashMap<>();

/**

* 放入缓存

*/

public void put(K key, V value) {

cache.put(key, new SoftReference<>(value));

}

/**

* 获取缓存项

*/

public V get(K key) {

SoftReference softRef = cache.get(key);

if (softRef != null) {

V value = softRef.get();

if (value == null) {

// 如果软引用已经被回收,则移除该项

// 也可以去数据库查询出相应的数据再放到map中,可以看实际情况选择

cache.remove(key);

}

return value;

}

return null;

}

/**

* 移除缓存项

*/

public void remove(K key) {

cache.remove(key);

}

/**

* 清理已经被回收的软引用

*/

public void cleanNullReferences() {

cache.entrySet().removeIf(entry -> entry.getValue().get() == null);

}

}

一个实际应用示例:图片缓存

public class ImageCache {

private static class ImageWrapper {

private final byte[] imageData;

private final String imageName;

public ImageWrapper(byte[] imageData, String imageName) {

this.imageData = imageData;

this.imageName = imageName;

}

// 模拟图片大小

public int getSize() {

return imageData.length;

}

}

private static class SoftImageCache {

private final Map> imageCache = new ConcurrentHashMap<>();

/**

* 加载图片到缓存

*/

public void loadImage(String imageName, byte[] imageData) {

imageCache.put(imageName,

new SoftReference<>(new ImageWrapper(imageData, imageName)));

}

/**

* 获取图片

*/

public ImageWrapper getImage(String imageName) {

SoftReference softRef = imageCache.get(imageName);

if (softRef != null) {

ImageWrapper image = softRef.get();

if (image != null) {

return image;

} else {

// 软引用已被回收,清除缓存条目

imageCache.remove(imageName);

}

}

return null;

}

/**

* 获取缓存大小

*/

public int getCacheSize() {

return imageCache.size();

}

}

// 使用示例

public static void main(String[] args) {

SoftImageCache imageCache = new SoftImageCache();

// 模拟加载大量图片

for (int i = 0; i < 100; i++) {

// 每张图片1MB

byte[] imageData = new byte[1024 * 1024];

imageCache.loadImage("image" + i, imageData);

System.out.println("加载图片: image" + i);

}

// 模拟访问图片

ImageWrapper image50 = imageCache.getImage("image50");

System.out.println("访问图片50: " + (image50 != null));

// 触发GC

System.gc();

// 再次访问图片

image50 = imageCache.getImage("image50");

System.out.println("GC后访问图片50: " + (image50 != null));

}

}

带有引用队列的增强版缓存

public class EnhancedSoftCache {

private final Map> cache = new ConcurrentHashMap<>();

private final ReferenceQueue queue = new ReferenceQueue<>();

/**

* 清理已被GC回收的对象

*/

private void processQueue() {

Reference ref;

while ((ref = queue.poll()) != null) {

// 遍历缓存找到并删除已经被回收的引用

cache.entrySet().removeIf(entry -> entry.getValue() == ref);

}

}

/**

* 存入缓存

*/

public void put(K key, V value) {

processQueue(); // 清理已回收的对象

cache.put(key, new SoftReference<>(value, queue));

}

/**

* 获取缓存值

*/

public V get(K key) {

processQueue(); // 清理已回收的对象

SoftReference ref = cache.get(key);

return ref != null ? ref.get() : null;

}

/**

* 获取缓存大小

*/

public int size() {

processQueue(); // 清理已回收的对象

return cache.size();

}

}

如上代码中定义了方法processQueue()来进行清理map中已经被回收的引用,然后在每个方法中都调用processQueue()方法来清理key和引用对象Reference,这两个对象都是被map强引用的。

使用场景和注意事项:

适用场景:

1.内存敏感的缓存实现

2.图片、文件等大对象的缓存

3.需要自动释放内存的场景

优点:

1.内存不足时自动释放

2.无需手动管理内存

3.比弱引用更持久

注意事项:

1.软引用对象的回收时机不确定

2.需要定期清理无效引用

3.不适合存储必须保持的数据

最佳实践:

1.配合引用队列使用

2.及时清理无效引用

3.合理设置缓存容量

4.考虑使用 WeakHashMap 作为替代方案

软引用是实现内存敏感型缓存的理想选择,它能在内存充足时保留缓存数据,在内存紧张时自动释放,从而在性能和内存使用之间取得平衡。

弱引用 (Weak Reference)

比软引用更弱,只要发生垃圾回收,弱引用对象就会被回收。常用于避免内存泄漏。

public class WeakReferenceDemo {

public static void main(String[] args) {

// 创建弱引用

WeakReference weakRef = new WeakReference<>(new String("Hello"));

// 获取弱引用对象

System.out.println("回收前:" + weakRef.get());

// 触发垃圾回收

System.gc();

// 弱引用对象可能被回收

System.out.println("回收后:" + weakRef.get());

}

}

虚引用 (Phantom Reference)

最弱的引用关系,随时可能被回收。必须和引用队列 (ReferenceQueue) 一起使用,主要用于跟踪对象被回收的状态。

public class PhantomReferenceDemo {

public static void main(String[] args) {

// 创建引用队列

ReferenceQueue queue = new ReferenceQueue<>();

// 创建虚引用

PhantomReference phantomRef = new PhantomReference<>(new String("Hello"), queue);

// 虚引用对象永远无法通过get()方法获取

System.out.println("虚引用对象:" + phantomRef.get()); // 永远返回null

// 触发垃圾回收

System.gc();

// 检查引用队列是否收到通知

Reference ref = queue.poll();

System.out.println("对象是否被回收:" + (ref != null));

}

}

终结器引用 (Final Reference)

当对象被垃圾回收器检测到不可达时,终结器引用会被加入引用队列,等待执行它的 finalize() 方法。

public class FinalReferenceDemo {

public static class ResourceClass {

private static List list = new ArrayList<>();

@Override

protected void finalize() throws Throwable {

// finalize方法会在对象被回收前调用

System.out.println("执行finalize方法");

// 可以在这里进行资源清理

list.add(this); // 可以在finalize中复活对象

super.finalize();

}

}

public static void main(String[] args) throws InterruptedException {

ResourceClass ref = new ResourceClass();

ref = null;

// 触发垃圾回收

System.gc();

// 等待finalize执行

Thread.sleep(1000);

}

}

总结:

1. 强引用:最常用,不会被回收

2.软引用:内存不足时回收

3.弱引用:垃圾回收时回收

4. 虚引用:随时可能被回收,必须配合引用队列使用

5.终结器引用:用于实现对象的finalize方法

这五种引用强度依次减弱:强引用 > 软引用 > 弱引用 > 虚引用/终结器引用

在实际应用中:

缓存场景常用软引用

防止内存泄漏常用弱引用(如WeakHashMap)

跟踪对象回收状态用虚引用

资源清理场景可能用到终结器引用

我在第一次看代码的时候曾有个疑惑就是,为什么要用引用队列呢?不用引用队列不是一样可以回收吗?不知道各位读者是否有和我一样的疑惑。

软引用和虚引用可以单独使用,也可以使用引用队列

因为个人感觉软引用和虚引用其实差别不大,只是回收的时机不同,所以下面从虚引用的角度叙述。

弱引用对象可以单独使用,也可以配合引用队列使用。当对象只有弱引用时,在下一次 GC 时一定会被回收,再由上面的map缓存案例也是可以得到,如果你用了引用队列,已经回收的对象都会放在引用队列里面,只虚要遍历引用队列就可以清理掉已经回收的数据,时间复杂度其实可以达到O(1),但是如果不用引用队列的话就根本不知道那些数据是被回收的,清理数据就会变得更加困难,就需要遍历整个 map。

public class WeakReferenceDemo {

public static void main(String[] args) throws InterruptedException {

// 创建引用队列

ReferenceQueue referenceQueue = new ReferenceQueue<>();

// 创建弱引用,关联引用队列

WeakReference weakRef = new WeakReference<>(new Object(), referenceQueue);

System.out.println("初始对象: " + weakRef.get());

// 触发GC

System.gc();

Thread.sleep(1000);

// 检查对象是否被回收

System.out.println("GC后对象: " + weakRef.get());

// 检查引用队列

Reference ref = referenceQueue.poll();

System.out.println("引用队列中的对象: " + (ref == weakRef)); // true表示弱引用对象已经进入队列

}

}

对比使用和不使用引用队列的区别

public class WeakReferenceComparisonDemo {

private static class Resource {

private String name;

private byte[] data = new byte[1024 * 1024]; // 1MB

public Resource(String name) {

this.name = name;

}

@Override

protected void finalize() {

System.out.println("Resource被回收: " + name);

}

@Override

public String toString() {

return "Resource{name='" + name + "'}";

}

}

public static void main(String[] args) throws InterruptedException {

// 不使用引用队列的弱引用

WeakReference weakRefWithoutQueue =

new WeakReference<>(new Resource("资源1"));

// 使用引用队列的弱引用

ReferenceQueue queue = new ReferenceQueue<>();

WeakReference weakRefWithQueue =

new WeakReference<>(new Resource("资源2"), queue);

System.out.println("GC前:");

System.out.println("无队列弱引用对象: " + weakRefWithoutQueue.get());

System.out.println("有队列弱引用对象: " + weakRefWithQueue.get());

// 触发GC

System.gc();

Thread.sleep(1000);

System.out.println("\nGC后:");

System.out.println("无队列弱引用对象: " + weakRefWithoutQueue.get());

System.out.println("有队列弱引用对象: " + weakRefWithQueue.get());

// 检查引用队列

Reference ref = queue.poll();

System.out.println("引用队列中的对象: " + (ref == weakRefWithQueue));

}

}

虚引用必须使用引用队列

虚引用必须配合引用队列使用,否则毫无意义。虚引用的主要作用是跟踪对象的回收状态,因为不管有没有gc,phantomRef.get()都无法得到数据,始终返回的是null。

public class PhantomReferenceDemo {

public static void main(String[] args) throws InterruptedException {

// 创建引用队列(必需)

ReferenceQueue referenceQueue = new ReferenceQueue<>();

// 创建一个大对象

byte[] data = new byte[1024 * 1024]; // 1MB

// 创建虚引用

PhantomReference phantomRef = new PhantomReference<>(data, referenceQueue);

// 清除强引用

data = null;

// 第一次检查

System.out.println("原始对象: " + phantomRef.get()); // 始终返回null

// 触发GC

System.gc();

Thread.sleep(1000);

// 检查引用队列

Reference ref;

while ((ref = referenceQueue.poll()) != null) {

System.out.println("对象被回收,引用进入队列");

// 可以在这里进行额外的清理工作

}

}

}

关键细节

引用对象的生命周期:

SoftReference/WeakReference 对象本身是普通对象,受强引用规则控制只有当没有强引用指向这些Reference对象时,它们才会被回收被引用的对象可能先于Reference对象被回收当不再需要Reference对象时,应该将其设为null使用引用队列来清理已经无用的Reference对象在缓存实现中要注意及时清理失效的引用

相关推荐

夷的五笔怎么打?夷的五笔拆分图.
注册送365元可提款

夷的五笔怎么打?夷的五笔拆分图.

📅 08-16 👁️ 5349
小时 到 分钟 转换器
注册送365元可提款

小时 到 分钟 转换器

📅 08-01 👁️ 7267
2025香烟网上购买渠道全解析:合法平台推荐与避坑指南
注册送365元可提款

2025香烟网上购买渠道全解析:合法平台推荐与避坑指南

📅 09-13 👁️ 2373