首页 > 编程语言 >Java中的五种引用方式底层详解

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

时间:2024-12-24 21:56:45浏览次数:5  
标签:Java 队列 System 回收 五种 引用 new public 详解

       在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<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("对象被回收,引用进入队列");
            // 可以在这里进行额外的清理工作
        }
    }
}

关键细节

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

标签:Java,队列,System,回收,五种,引用,new,public,详解
From: https://blog.csdn.net/xweiran/article/details/144698196

相关文章

  • JAVA面向对象(二)封装
    数据的守护者封装是面向对象编程的重要特性之一,它将数据和操作数据的方法紧密地结合在一起,并对外部隐藏了数据的具体实现细节。这样做的好处是多方面的。一方面,它保护了数据的完整性。例如,在Person类中,如果我们直接将age成员变量暴露给外部,那么可能会出现不合理的赋值情况,比如设......
  • JavaDay2
    JavaDay2选择结构Switchpackageshujia.day02.ketang;importjava.util.Scanner;/*Switch选择语句:语句定义格式:switch(表达式){case常量值1:表达式1;break;case......
  • 跟着问题学23番外——反向传播算法理论及pytorch自动求导详解
    前向传播与反向传播在单层神经网络的优化算法里,我们讲到优化算法是为了寻找模型参数使得网络的损失值最小,这里详细介绍一下应用的基础——反向传播算法。在神经网络中,梯度计算是通过反向传播算法来实现的。反向传播算法用于计算损失函数相对于网络参数(如权重和偏置)的梯度,从而......
  • 【开源免费】基于SpringBoot+Vue.JS保密信息学科平台系统(JAVA毕业设计)
    本文项目编号T112,文末自助获取源码\color{red}{T112,文末自助获取源码}......
  • 【开源免费】基于SpringBoot+Vue.JS学生网上请假系统(JAVA毕业设计)
    本文项目编号T111,文末自助获取源码\color{red}{T111,文末自助获取源码}......
  • java反射详讲
    好的!以下是关于Java反射的详细讲解(约5000字左右)。内容包括基础概念、反射的优缺点、基本用法,以及典型案例。Java反射详解反射是Java中的一项强大机制,允许程序在运行时动态获取类的相关信息,并对其进行操作。这项特性使得Java程序具备了极大的灵活性,适用于框架开发、工......
  • 2025年Java面试合集,终于整理好了
    进大厂是大部分程序员的梦想,而进大厂的门槛也是比较高的,所以这里整理了一份阿里、美团、滴滴、头条等大厂面试大全,其中概括的知识点有:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux......
  • 【openGauss】Java层传参到openGauss使用拼接字符串拆分为数组方案解决in中用foreach
    【openGauss】Java层传参到openGauss使用拼接字符串拆分为数组方案解决in中用foreach拼接的32767限制一、sql格式二、使用说明三、测试四、SQL解析五、其他说明一、sql格式比如delete:DELETEFROM表名WHERE主键ID字段=any(string_to_array(#{fieldList,jdb......
  • JavaDay2
    JavaDay2选择结构Switchpackageshujia.day02.ketang;importjava.util.Scanner;/*Switch选择语句:语句定义格式:switch(表达式){case常量值1:表达式1;break;case......
  • Java 重写(Override)与重载(Overload)
    重写(Override)重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。在面向对象原则里,重写意味着可以重写任何现有方法。......