开发者

Java资源管理和引用体系的使用详解

目录
  • 1、Java的引用体系
    • 1、强引用 (Strong Reference)
    • 2、软引用 (Soft Reference)
    • 3、弱引用 (Weak Reference)
    • 4、虚引用 (Phantom Reference)
  • 2、finalize
    • 2.1、所属领域
    • 2.2、作用
    • 2.3、问题
    • 2.4、使用场景
  • 3、try-with-resources
    • 3.1、所属领域
    • 3.2、作用
    • 3.3、优势
  • 4、Cleaner
    • 4.1、Cleaner 的优势
    • 4.2、底层机制
    • 4.3、执行流程
  • 总结

    Java中的引用类型不仅是内存管理的概念,更是资源管理的重要工具。合理使用不同类型的引用可以有效地管理内存敏感资源,防止内存泄漏和资源浪费。

    1、java的引用体系

    如下图所示,可分为四种:强引用、软引用、弱引用和虚引用。

    Java资源管理和引用体系的使用详解

    以下是对四种引用的类比,可以让大家有个更好的理解。

    Java资源管理和引用体系的使用详解

    1、强引用 (Strong Reference)

    基本特性

    Object obj = new Object(); // 这就是强引用
    • 默认引用类型:所有普通对象引用都是强引用。
    • GC行为:只要强引用存在,对象就不会被回收。
    • 内存泄漏:无意保留的强引用是内存泄漏的主因。

    代码示例:

    public class StrongReferenceExample {
        private List<String> cache = new ArrayList<>();
        
        public void addToCache(String data) {
            cache.add(data); // 强引用保留对象
        }
        
        public void clearCache() {
            cache.clear(); // 必须显式清除
        }
    }

    2、软引用 (Soft Reference)

    1.基本特性

    SoftReference<Object> softRef = new SoftReference<>(new Object());

    GC行为

    • 内存充足时:保留对象
    • 内存不足时:回收对象(在OOM之前)
    • 缓存特性:适合实现内存敏感缓存

    2.核心特点

    • 动态调整缓存大小
    • 当系统内存充足时,可以缓存更多数据以提高性能
    • 当内存紧张时,自动清理部分缓存,避免内存溢出

    优先级管理

    • 对缓存项设置优先级(如LRU最近最少使用)
    • 内存不足时优先清理低优先级数据

    3.响应内存压力

    • 监听JVM内存使用情况(如 Runtime.getRuntime().freeMemory())。
    • 或使用 Java.lang.ref.SoftReference(软引用)让GC在内存不足时自动回收。
    • 实现方式

    Java资源管理和引用体系的使用详解

    实战示例:基于内存压力softReference的图片缓存

    public class ImageCache {
        private final Map<String, SoftReference<Bitmap>> cache = new HashMap<>();
        
        public Bitmap getImage(String key) {
            SoftReference<Bitmap> ref = cache.get(key);
            return ref != null ? ref.get() : null;
        }
        
        public void putImage(String key, Bitmap image) {
            cache.put(key, new SoftReference<>(image));
        }
    }

    基于Runtime.freeMemoy()来实现。

    import java.lang.ref.SoftReference;
    import java.util.HashMap;
    import java.util.Map;
    
    public class MemorySensitiveCache<K, V> {
        private final Map<K, SoftReference<V>> cache = new HashMap<>();
    
        // 存入缓存(使用软引用,内存不足时可被GC回收)
        public void put(K key, V value) {
            cache.put(key, new SoftReference<>(value));
        }
    
        // 从缓存读取(可能返回null如果已被GC回收)
        public V get(K key) {
            SoftReference<V> ref = cache.get(key);
            return (ref != null) ? ref.get() : null;
        }
    
        // 示例:内存监控线程
        public void startMemoryMonitor() {
            new Thread(() -> {
                while (true) {
                    long freeMem = Runtime.getRuntime().freeMemory();
                    if (freeMem < 1024 * 1024) { // 如果剩余内存 < 1MB
                        System.out.println("内存紧张,清理缓存...");
                        cache.clear();
                    }
                    try { Thread.sleep(1000); } catch (InterruptedException e) {}
                }
     http://www.devze.com       }).start();
        }
    }

    3、弱引用 (Weak Reference)

    3.1、基本特性

    • GC行为下次GC时立即回收(无论内存是否充足)
    • 规范映射:适合存储元数据等辅助信息
    WeakReference<Object> weakRef = new WeakReferenjsce<>(new Object());

    代码示例:

    import java.util.WeakHashMap;
    
    public class WeakHashMapExample {
        public static void main(String[] args) {
            WeakHashMap<Object, String> map = new WeakHashMap<>();
            
            Object key = new Object();
            map.put(key, "Value");
            
            System.out.println("GC 前: " + map.size()); // 1
            
            key = null; // 断开强引用
            System.gc(); // 触发 GC
            
           mxkxOh System.out.println("GC 后: " + map.size()); // 0(Entry 被自动清理)
        }
    }

    4、虚引用 (Phantom Reference)

    基本特性

    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

    GC行为

    • 对象变得虚引用可达
    • 引用加入队列
    • 最终回收内存

    特殊限制get()方法永远返回null

    主要用途:精确控制资源清理时机

    5、常见问题

    Q1: WeakHashMap的key是弱引用,那value呢?

    A: 只有key是弱引用,value是强引用。需要确保value不间接引用key,否则会内存泄漏。

    Q2: 虚引用get()为什么返回null?

    A: 这是设计使然,虚引用仅用于跟踪回收事件,不应再访问将死对象。

    Q3: 软引用和弱引用谁更适合缓存?

    A: 软引用适合主缓存(内存敏感),弱引用适合辅助缓存(立即释放)。

    四种引用的区别和联系如下图所示:

    Java资源管理和引用体系的使用详解

    2、finalize

    finalize:垃圾回收的终结机制。

    2.1、所属领域

    • 垃圾回收(Garbage Collection) 的补充机制。
    • 属于 java.lang.Object 的基础方法,与 JVM 的 GC 流程相关。

    2.2、作用

    • 在对象被垃圾回收前,JVM 会调用 finalize() 方法(如果重写了该方法),用于执行最后的清理操作(如关闭非内存资源)。

    2.3、问题

    • 不可靠:JVM 不保证 finalize() 的执行时机(甚至可能不执行)。
    • 性能开销:拖慢 GC 效率(对象需要至少两次 GC 才能被回收)。
    • 已被废弃:Java 9 开始标记为 @Deprecated,推荐用 java.lang.ref.CleanerAutoCloseable 替代。

    2.4、使用场景

    想象你租了一间房:

    • 正常情况:退租时主动交还钥匙(相当于 close() 方法)
    • 忘记还钥匙:房东最后会自己来收钥匙(相当于 finalize()
    • 风险:房东可能很久后才来,期编程间钥匙被别人捡到可能不安全

    代码示例:

    public class Room {
        private static final Set<String> LEAKED_KEYS = new HashSet<>();
        private String roomKey;
    
        public Room(String key) {
            this.roomKey = key;
            LEAKED_KEYS.add(key); // 登记钥匙
            System.out.println("拿到钥匙进入房间: " + key);
        }
    
        // 主动还钥匙(推荐方式)
        public void returnKey() {
            if (roomKey != null) {
                System.out.println("主动归还钥匙: " + roomKey);
                LEAKED_KEYS.remove(roomKey); // 注销钥匙
                roomKey = null;
            }
        }
    
        @Override
        protected void finalize() throws Throwable {
            if (roomKey != null) {
                System.out.println("警告!钥匙未被归还: " + roomKey + " (内存泄漏)");
                LEAKED_KEYS.remove(roomKey); // 清理登记
            }
        }
    
        public static void main(String[] args) {
            // 好租客
            Room goodRoom = new Room("A101");
            goodRoom.returnKey();
    
            // 坏租客
            new Room("B202"); // 直接不保存引用
            
            System.gc();
            try { Thread.sleep(1000); } catch (Exception e) {}
            
            System.out.println("未归还的钥匙: " + LEAKED_KEYS);
        }
    }

    输出:

    拿到钥匙进入房间: A101

    主动归还钥匙: A101

    拿到钥匙进入房间: B202

    警告!钥匙未被归还: B202 (内存泄漏)

    未归还的钥匙: []

    结论

    • finalize() 只会在 GC 回收对象时执行,而 System.gc() 只是建议 GC 运行,不保证立即执行。
    • finalize() 不保证一定执行,如果程序编程客栈退出前 GC 没运行,它就不会被调用。
    • 不应该依赖 finalize() 管理资源(如文件、数据库连接),而应该使用 try-with-resources 或手动 close()
    • 同步调用 finalize() 会阻塞 GC 线程,降低性能。

    3、try-with-resources

    属于java的资源管理 → try-with-resources + AutoCloseable。

    3.1、所属领域

    • 资源管理(Resource Management),尤其是需要显式释放的资源(如文件、数据库连接、网络套接字)。
    • 基于 AutoCloseable 接口(Java 7 引入),属于语法糖(Syntactic Sugar)。

    3.2、作用

    • 自动在 try 代码块结束后调用资源的 close() 方法,避免资源泄露
    • 比手动 try-catch-finally 更简洁安全。

    3.3、优势

    • 确定性释放:资源离开 try 块立即关闭。
    • 代码简洁:减少嵌套和样板代码。
    • 异常友好:支持抑制异常(Suppressed Exceptions)。

    代码示例:

    // 实现 AutoCloseable,用 try-with-resources 确保资源释放
    class Resource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed!");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            try (Resource r = new Resource()) {
                // 使用资源
            } // 自动调用 close()
        }
    }

    finalize和try-with-resources的区别如下图所示:

    Java资源管理和引用体系的使用详解

    替代 finalize 的方案:

    • CleanerPhantomReference:提供更可控的清理机制(但复杂度高)。
    • AutoCloseable + try-with-resources:首选方案。

    资源管理的最佳实践

    • 优先实现 AutoCloseable 接口,而非依赖 finalize
    • 对于必须手动管理的资源(如原生内存),结合 ByteBuffer.allocateDirectCleaner 使用。

    4、Cleaner

    (Java 9+)java.lang.ref.Cleaner。

    如下图所示 :由phant虚引用和队列来实现。

    Java资源管理和引用体系的使用详解

    4.1、Cleaner 的优势

    确定性更强:通过虚引用(PhantomReference)和引用队列实现

    更安全:清理操作在独立线程执行,不影响主线程

    性能更好:不干扰正常垃圾回收流程

    可控性高:可以显式注册/取消清理操作

    4.2、底层机制

    • 引用类型:使用 PhantomReference(虚引用)
    • 引用队列:被清理对象进入引用队列时触发清理
    • 守护线程:专门的清理线程处理队列中的对象

    代码示例如下:

    // Cleaner 内部实现伪代码
    public class Cleaner {
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
        private final PhantomReference<Object> phantomRef;
        private final Runnable cleanupAction;
        
        public static Cleaner create() {
            return new Cleaner();
        }
        
        public Cleanable register(Object obj, Runnable action) {
            phantomRef = new PhantomReference<>(obj, queue);
            this.cleanupAction = action;
            startCleanerThread();  // 启动监控线程
            return new CleanableImpl();
        }
        
        private void startCleanerThread() {
            Thread thread = new Thread(() -> {
                while (true) {
                    Reference<?> ref = queue.remove(); // 阻塞等待
                    if (ref == phantomRef) {
                        cleanupAction.run();
                    }
                }
            });
            thread.setDaemon(true);
            thread.start();
        }
    }

    4.3、执行流程

    Java资源管理和引用体系的使用详解

    import java.lang.ref.Cleaner;
    
    public class CleanerDemo {
        private static final Cleaner cleaner = Cleaner.create();
    
        static class Resource implements Runnable {
            @Override
            public void run() {
                System.out.println("Cleaner action executed!");
            }
        }
    
        public static void main(String[] args) {
            Resource resource = new Resource();
            cleaner.register(new Object(), resource); // 注册清理动作
            System.gc();
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
        }
    }

    相比较于finalize和cleaner的区别:

    Java资源管理和引用体系的使用详解

    总结

    通过合理组合这些引用类型,可以构建出既高效又安全的资源管理系统。

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜