在GC算法的可达性算法中描述的对象引用,一般指的是强引用,即是GCRoot对象对普通对象有引用关系,只要这层关系存在,普通对象就不会被回收,而在Java中一共有五种引用关系。
目录
强引用 (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<byte[]> 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<K, V> {
// 使用ConcurrentHashMap存储缓存项
private final ConcurrentHashMap<K, SoftReference<V>> cache = new ConcurrentHashMap<>();
/**
* 放入缓存
*/
public void put(K key, V value) {
cache.put(key, new SoftReference<>(value));
}
/**
* 获取缓存项
*/
public V get(K key) {
SoftReference<V> 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<String, SoftReference<ImageWrapper>> 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<ImageWrapper> 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<K, V> {
private final Map<K, SoftReference<V>> cache = new ConcurrentHashMap<>();
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
/**
* 清理已被GC回收的对象
*/
private void processQueue() {
Reference<? extends V> 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<V> 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<String> 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<String> queue = new ReferenceQueue<>();
// 创建虚引用
PhantomReference<String> 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<ResourceClass> 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<Object> referenceQueue = new ReferenceQueue<>();
// 创建弱引用,关联引用队列
WeakReference<Object> 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<Resource> weakRefWithoutQueue =
new WeakReference<>(new Resource("资源1"));
// 使用引用队列的弱引用
ReferenceQueue<Resource> queue = new ReferenceQueue<>();
WeakReference<Resource> 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<Object> referenceQueue = new ReferenceQueue<>();
// 创建一个大对象
byte[] data = new byte[1024 * 1024]; // 1MB
// 创建虚引用
PhantomReference<byte[]> 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("对象被回收,引用进入队列");
// 可以在这里进行额外的清理工作
}
}
}
关键细节
引用对象的生命周期:
标签:Java,队列,System,回收,五种,引用,new,public,详解 From: https://blog.csdn.net/xweiran/article/details/144698196
- SoftReference/WeakReference 对象本身是普通对象,受强引用规则控制
- 只有当没有强引用指向这些Reference对象时,它们才会被回收
- 被引用的对象可能先于Reference对象被回收
- 当不再需要Reference对象时,应该将其设为null
- 使用引用队列来清理已经无用的Reference对象
- 在缓存实现中要注意及时清理失效的引用