当前位置: 首页 > article >正文

Java 集合框架大师课:集合框架的暗黑料理(六)

🔮Java 集合框架大师课:集合框架的暗黑料理(六)——弱引用与幽灵队列


第一章 弱引用:Java世界的塑料兄弟情 💔

四大引用类型生死簿

// 四类引用生死实验
Object strongObj = new Object();                      // 强引用:钢铁侠 🦾
SoftReference<Object> softRef = new SoftReference<>(new Object()); // 软引用:橡皮盾 🛡️
WeakReference<Object> weakRef = new WeakReference<>(new Object()); // 弱引用:纸片人 📄
ReferenceQueue<Object> phantomQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), phantomQueue); // 虚引用:幽灵 👻

System.gc();  // 召唤GC阎王

System.out.println("强引用存活: " + strongObj);          // java.lang.Object@6d311334
System.out.println("软引用存活: " + softRef.get());       // null(若内存充足则可能存活)
System.out.println("弱引用阵亡: " + weakRef.get());        // null 
System.out.println("虚引用队列: " + phantomQueue.poll()); // 出现PhantomReference对象

🔗 引用类型生存法则

在这里插入图片描述

🔑 四类引用代码核心知识点

在这里插入图片描述

📌 关键特性解析
  1. 强引用生存法则
Object strongObj = new Object(); // 钢铁长城永不倒
  • 必须显式置为 null才会被回收
  • 集合类中的元素引用默认都是强引用
  1. 软引用缓存策略
SoftReference<Object> cache = new SoftReference<>(data);
  • 内存不足时的安全气囊
  • 适合实现图片缓存等需要自动清理的场景
  • 可通过 -Xmx参数调整内存阈值观察回收行为
  1. 弱引用速死特性
WeakHashMap<Key,Value> map = new WeakHashMap<>();
  • 常用于监听器模式防止内存泄漏
  • ReferenceQueue配合可实现精确清理
  1. 虚引用幽灵特性
PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
  • get()永远返回 null,需配合队列使用
  • 必须手动调用 clear()释放资源
  • 唯一能感知对象finalize后的引用类型

⚔️ 实战技巧与避坑指南

技巧一:缓存系统设计
// 三级缓存架构示例
Map<String, SoftReference<Bitmap>> memoryCache = new ConcurrentHashMap<>();
Map<String, WeakReference<Bitmap>> activityCache = new WeakHashMap<>();
DiskLruCache diskCache; // 持久化存储

// 内存不足时自动释放SoftReference缓存
// 界面关闭时自动清除WeakHashMap缓存
技巧二:监控引用状态
// 虚引用监控线程模板
new Thread(() -> {
    while(true) {
        Reference<?> ref = phantomQueue.remove();
        cleanNativeResource(ref); // 释放关联的Native资源
    }
}).start();
避坑指南表
错误场景后果解决方案
软引用与强引用长期共存缓存永不失效用WeakHashMap管理缓存键
虚引用未及时clear()Native资源泄漏队列监听线程中显式清理
弱引用保存大对象频繁GC影响性能配合SoftReference使用
直接修改ReferenceQueue监控机制失效使用remove/poll方法获取引用

🔥 高阶调试技巧

// 强制验证软引用行为(IDEA调试技巧)
public static void testSoftReference() {
    Object obj = new byte[1024 * 1024 * 10]; // 分配10MB
    SoftReference<Object> ref = new SoftReference<>(obj);
    obj = null; // 断开强引用
  
    // 在IDEA的Memory工具中手动执行GC
    // 观察ref.get()在不同内存压力下的状态
}

📜 引用类型生存法则表

引用类型GC回收条件典型应用场景生存强度 🌡️生存条件回收灵敏度代码标识
强引用(Strong)永不自动回收核心业务对象钛合金级 🛡️存在引用链⚡️ 最低= 直接赋值
软引用(Soft)内存不足时回收缓存系统橡皮级 🧽内存不足时🌧️ 中等SoftReference 包裹
弱引用(Weak)发现即回收WeakHashMap纸片级 📄下次GC必定回收🔥 高WeakReference 包裹
虚引用(Phantom)随时可能回收资源清理跟踪空气级 🌬️随时可能回收💨 最高PhantomReference 包裹

在这里插入图片描述

📌 黄金法则:强引用是默认选择,软引用用于缓存,弱引用处理临时数据,虚引用只做监控。理解各引用类型的回收触发边界,才能写出内存安全的Java程序!


第二章 幽灵队列:阴阳两界的邮差 👻📬

2.1 引用队列工作原理

// 监控对象死亡的"死亡笔记" 📓
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
WeakReference<byte[]> ref = new WeakReference<>(new byte[1024*1024], queue);

System.out.println(queue.poll()); // null (对象还活着)
System.gc();

Reference<? extends byte[]> deadRef = queue.remove(500);
System.out.println(deadRef); // java.lang.ref.WeakReference@452b3a41 (灵魂已收割)

引用队列三定律

  1. 对象被GC标记为"死亡"时入队
  2. 队列中的引用对象已无实体
  3. 通过队列可实现资源清理

📌 引用对象生命周期图

在这里插入图片描述

💡 实战应用场景

// 案例:大型图片缓存清理器
public class ImageCache {
    private final ReferenceQueue<BufferedImage> queue = new ReferenceQueue<>();
    private final List<WeakReference<BufferedImage>> cache = new ArrayList<>();

    // 添加缓存并关联队列
    public void add(BufferedImage image) {
        cache.add(new WeakReference<>(image, queue));
    }

    // 启动清理线程
    public void startCleaner() {
        new Thread(() -> {
            while (true) {
                try {
                    Reference<?> ref = queue.remove();
                    // 找到对应的缓存项并移除
                    cache.removeIf(wr -> wr == ref);
                } catch (InterruptedException e) { /*...*/ }
            }
        }).start();
    }
}

2.2 幽灵引用实战:监控大对象销毁

// 创建1GB大对象监控
ReferenceQueue<byte[]> phantomQueue = new ReferenceQueue<>();
PhantomReference<byte[]> phantomRef = 
    new PhantomReference<>(new byte[1024*1024*1024], phantomQueue);

new Thread(() -> {
    try {
        System.out.println("等待对象投胎...");
        phantomQueue.remove(); // 阻塞直到对象销毁
        System.out.println("检测到1GB内存释放!🎉");
        // 触发后续清理操作
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

在这里插入图片描述

🔑 关键知识点拆解

  1. 虚引用三大法则
PhantomReference<byte[]> ref = new PhantomReference<>(obj, queue);
System.out.println(ref.get()); // 永远输出null 👻
  • 必须关联 ReferenceQueue
  • get()始终返回 null
  • 对象销毁后才会入队
  1. 监控线程设计要点
phantomQueue.remove(); // 阻塞式监听
// 替代方案(非阻塞轮询):
while(true) {
    Reference<?> ref = phantomQueue.poll();
    if(ref != null) process(ref);
    Thread.sleep(1000);
}
  1. 资源释放最佳实践
phantomRef.clear(); // 必须手动清除
Runtime.getRuntime().gc(); // 建议但不强制GC
System.runFinalization(); // 执行finalize方法

💡 实战进阶技巧

// 案例:大文件句柄自动关闭
class FileWatcher {
    private static final ReferenceQueue<FileChannel> QUEUE = new ReferenceQueue<>();
    private final Map<PhantomReference<FileChannel>, Closeable> resources = new ConcurrentHashMap<>();

    void watch(FileChannel channel, Closeable resource) {
        PhantomReference<FileChannel> ref = new PhantomReference<>(channel, QUEUE);
        resources.put(ref, resource);
  
        new Thread(() -> {
            try {
                QUEUE.remove();
                resources.remove(ref).close(); // 自动关闭句柄
                System.out.println("文件资源已释放");
            } catch (Exception e) { /*...*/ }
        }).start();
    }
}

第三章 WeakHashMap 源码屠宰场 🔪

3.1 expungeStaleEntries 方法解析

// 清理僵尸Entry的核心代码 🧟
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null;) {
        synchronized (queue) {
            Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);
    
            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    e.value = null; // 帮助GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

3.2 哈希桶结构异变史

在这里插入图片描述

📌 WeakHashMap特性速查表

特性普通HashMapWeakHashMap注意事项 ⚠️
Key回收机制永不回收GC时回收Value需无其他引用
迭代器稳定性稳定可能突变不要缓存entrySet
containsValue性能O(n)可能更慢存在僵尸Entry干扰
内存敏感场景不适用推荐使用适合做缓存底层存储

第四章 暗黑实战:用幽灵队列实现连接池监控 🕶️

4.1 数据库连接池监控器

// 监控Connection关闭情况
public class ConnectionMonitor {
    private static final ReferenceQueue<Connection> QUEUE = new ReferenceQueue<>();
    private static final Map<PhantomReference<Connection>, String> TRACE_MAP = new ConcurrentHashMap<>();

    public static Connection wrap(Connection realConn) {
        PhantomReference<Connection> ref = 
            new PhantomReference<>(realConn, QUEUE);
        TRACE_MAP.put(ref, "创建于: " + new Date());
  
        return new ProxyConnection(realConn);
    }

    static {
        new CleanerThread().start();
    }

    private static class CleanerThread extends Thread {
        public void run() {
            while (true) {
                try {
                    PhantomReference<?> ref = (PhantomReference<?>) QUEUE.remove();
                    String info = TRACE_MAP.remove(ref);
                    System.out.println("连接关闭警告: " + info + " 未正确释放!🚨");
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

4.2 监控效果演示

Connection conn = ConnectionMonitor.wrap(datasource.getConnection());
conn.close(); // 正确关闭无事发生

Connection leakedConn = ConnectionMonitor.wrap(datasource.getConnection());
// 忘记close,GC后输出:
// 连接关闭警告: 创建于: Wed Mar 15 14:11:22 CST 2025 未正确释放!🚨

第五章 暗黑陷阱集:引用使用七大罪 💀

5.1 常见翻车现场

// 陷阱1:强引用藏匿
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "Value");
key = null; // 但map仍然持有Entry的强引用!😱

// 陷阱2:值对象持有key
class User {
    String name;
    // 错误示范!value持有key引用形成环
    Map<User, String> map = new WeakHashMap<>(); 
}

5.2 生存法则对照表

正确姿势 ✅错误姿势 ❌后果 💥
使用短期局部变量作为Key用Long-term对象作为Key永远无法回收
Value不持有Key引用Value内部包含Key内存泄漏
定期调用size()/isEmpty()仅依赖GC僵尸Entry堆积
配合ReferenceQueue使用直接监控对象状态无法感知精确回收时机

🚀 下期预告:《集合框架的量子力学——CopyOnWrite与CAS魔法》 ⚛️

// 彩蛋代码:ConcurrentHashMap的量子态put
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 当多个线程同时put时...
    if ((fh = f.hash) == MOVED)
        tab = helpTransfer(tab, f); // 进入量子纠缠态!
    else {
        synchronized (f) { // 坍缩为经典态
            // ...省略碰撞处理代码
        }
    }
}

🌌 灵魂拷问 :当两个线程同时触发扩容,ConcurrentHashMap如何维持时空连续性?答案下期揭晓!


http://www.kler.cn/a/585677.html

相关文章:

  • 数据结构(三)——链表
  • 流水线(Pipeline)
  • Linux 进程的创建、终止、等待与程序替换函数 保姆级讲解
  • KICK第五课:Mac 系统下安装 Xcode 或 Clang
  • C++初阶——类和对象(一)
  • 3DS模拟器使用(pc+安卓)+金手指+存档互传
  • visual studio编译fortran
  • Springboot项目发送请求
  • 什么是提示词工程,有哪些开源项目
  • Android Studio执行Run操作报Couldn‘t terminate previous instance of app错误
  • (动态规划 区间dp/dfs 最长回文子序列)leetcode 516
  • MATLAB R2024b 安装教程
  • 深入理解 ALSA 声卡驱动:从理论到实践,解决嵌入式 Linux 声卡无声问题
  • 基于Asp.net的医院病历管理系统
  • 射频相关概念
  • MySQL | MySQL表的增删改查(CRUD)
  • 使用 Swiss Table 如何实现更快的 Go map
  • 大模型高效优化技术全景解析:微调、量化、剪枝、梯度裁剪与蒸馏
  • chmod用法
  • 基于Spring Boot的网上宠物店系统的设计与实现(LW+源码+讲解)