网站批量修改,百度网盘资源分享,关键词查询工具,百度网站开发业务前言#xff1a; 堆回收主要作用是清理Java堆中不再被引用的对象#xff0c;释放内存空间#xff0c;避免内存泄漏。它通过标记可回收对象#xff0c;再用合适算法回收#xff0c;保障内存高效利用。同时#xff0c;堆回收机制影响程序性能#xff0c;了解其原理能优化内…
前言 堆回收主要作用是清理Java堆中不再被引用的对象释放内存空间避免内存泄漏。它通过标记可回收对象再用合适算法回收保障内存高效利用。同时堆回收机制影响程序性能了解其原理能优化内存管理提升应用稳定性和运行效率。 堆是 Java 运行时环境的基石其高效管理直接决定了应用的性能、稳定性和可扩展性。深入理解堆内存机制如分代策略、GC 算法是 Java 开发者必备的核心技能。
接下来让我们来深入探寻堆的内存机制。
引用计数法和可达性分析法
java堆有一个最大的特点就是自动垃圾回收这一部分不需要我们程序员来操心那它怎么知道什么时候把垃圾清理掉它怎么知道哪部分是垃圾
在Java中的对象是否可以被回收是根据对象是否被引用来决定的。如果对象被引用了说明改对象还要使用不能回收。
引用计数法
核心思想 每个对象都自带一个“小计数器”。这个计数器记录着当前有多少个“用户”正在引用使用它。当一个新的用户开始使用它时计数器加 1当一个用户用完它时计数器减 1。每次减一后java虚拟机都会进行扫描当发现当计数器归零时意味着没有任何用户需要它了系统就可以立刻把它回收。
优点
实时性每次执行减一操作之后都会进行扫描一旦发现为0立即回收。没有延迟这对于需要及时释放资源如文件句柄、数据库连接、网络端口的场景非常有利。
实现相对简单核心逻辑就是计数器加减和归零判断概念清晰易懂。
缺点
频繁的计数器更新开销每次引用和取消引用都会维护计数器对系统的性能会有一定的影响。
计数器存储开销每个对象都会存储一个计数器如果对象很多那计数器也很多。
循环引用问题 (致命弱点)比如在堆中有两个对象然后这两个对象互相引用那他们的计数器永远都不会归零。永远都是1它们就永远不可能被回收。 可达性分析算法
可达性分析算法Reachability Analysis是自动内存管理的核心算法用于判定堆内存中的对象是否存活。其核心思想是分为两个对象一个叫GC Roots一个叫普通对象。通过一系列称为“GC Roots”的根对象作为起点遍历对象引用链所有能被遍历到的对象是“存活”的不能被遍历到的对象是“垃圾”。 GC Roots 是垃圾回收的起点以下对象可作为 GC Roots 虚拟机栈中的引用对象如方法局部变量 理由方法执行时栈帧中的变量正在使用其引用的对象不能回收。 方法区中类静态属性引用的对象 理由静态变量随类加载存在生命周期长引用的对象需保留。 方法区中常量引用的对象 理由常量池中的常量引用的对象如字符串常量不能被回收。 本地方法栈中 JNI 引用的对象 理由本地代码如 C正在使用的 Java 对象不能被回收。 活跃线程的引用对象比如 理由线程正在执行其引用的对象不能回收。 一句话GC Roots 就是“程序当前绝对离不开的对象”绝对不会被回收。 我们一直提到的有没有被引用其实引用也分有类型。
五种引用类型
强引用 (Strong Reference)
Object obj new Object()这种最常见的引用obj引用了Object 在堆中的对象只要强引用还在就不可能被回收。如果objnull这个时候堆中的对象就会被回收了。
实现方式普通对象引用
// 创建强引用
Object obj new Object();// 解除强引用使对象可回收
obj null;
软引用 (Soft Reference)
它是相对于强引用弱一点的引用关系软引用指向的对象在 内存不足会被垃圾回收器回收。然后把这部分内存分给其他对象。
软引用本身作为一个对象也会被回收但其回收条件与普通对象类似当没有任何强引用指向该软引用对象时就会被垃圾回收器回收。
实现类java.lang.ref.SoftReference
// 创建软引用
SoftReferenceObject softRef new SoftReference(new Object());// 获取对象可能返回 null
Object obj softRef.get(); // 使用引用队列可选
ReferenceQueueObject queue new ReferenceQueue();
SoftReferenceObject softRefWithQueue new SoftReference(new Object(), queue);
软引用在 Java 中用于实现内存敏感的缓存适用于以下场景
图片缓存存储图片资源当内存不足时自动释放避免 OOM。网页缓存缓存网页内容在内存紧张时优先回收提升性能。大对象缓存缓存计算结果、数据库查询结果等减少重复计算。内存敏感的中间数据存储临时计算结果在内存不足时优雅降级。
核心优势在保证应用正常运行的前提下最大限度利用内存提升性能。
软引用通常和一个引用队列一起联用
引用队列 (ReferenceQueue) 的作用 创建软引用时传入一个引用队列 当软引用指向的对象 被垃圾回收器回收后这个 软引用对象本身会被 JVM 自动放入关联的队列中。 程序可以定期检查这个队列知道哪些软引用持有的对象已经被回收了从而进行一些清理工作例如从缓存映射中移除对应的键。
弱引用 (Weak Reference)
弱引用和软引用的本质区别就是不管内存够不够它所关联的对象在垃圾回收时都会被回收。它完全不影响它所关联对象存活。
弱引用通常和引用队列搭配使用当对象被回收时弱引用会被放入队列程序可借此感知对象的生命周期。
核心特点弱引用关联的对象在没有强引用时会被立即回收适合需要 “自动过期” 的场景。与软引用的区别软引用在内存不足时才会回收对象而弱引用只要 GC 运行就会回收不管内存是否充足。
实现类java.lang.ref.WeakReference
// 创建弱引用
WeakReferenceObject weakRef new WeakReference(new Object());// 获取对象可能很快变 null
Object obj weakRef.get();// 典型应用WeakHashMap
WeakHashMapKey, Value weakMap new WeakHashMap();
为什么要用弱引用 缓存场景防止内存泄漏 例子HashMap 存储图片对象时若用强引用即使图片不再被使用也无法被回收导致内存占用过高。而用弱引用存储键或值当图片不再被其他地方引用时会被 GC 自动清理避免缓存 “占着内存不放”。 解决回调导致的内存泄漏 场景GUI 程序中窗口Window持有监听器Listener的强引用若监听器又反向引用窗口会形成循环引用。用弱引用定义监听器对窗口的引用可让窗口在关闭后正常被回收。 Map 结构的弱键设计 比如WeakHashMap它的键是弱引用当键对象不再被强引用时GC 会自动清理对应的键值对适合实现 “临时关联” 的数据结构如缓存自动过期。
弱引用的特性是 “对象没有强引用时会被 GC 直接回收”但程序无法主动知道 GC 何时回收对象。引用队列的作用就是 当 GC 回收弱引用关联的对象时JVM 会自动把这个弱引用对象放入队列程序通过监听队列就能得知 “对象已被回收”从而执行后续处理如删除缓存、更新状态等。 虚引用 (Phantom Reference) 虚引用是所有引用类型中最弱的必须和引用队列ReferenceQueue一起用对象被回收前虚引用会被放入队列此时程序可以做后续处理但对象本身已经 “必死无疑” 了。
存在的唯一目的只是为了在对象被垃圾回收时收到一个 “通知”但完全不影响对象的生命周期。使用场景常用于管理堆外内存比如 NIO 的 DirectBuffer。当虚引用关联的对象被回收时JVM 会把这个虚引用加入到一个队列里程序可以通过监听这个队列手动释放堆外资源因为堆外内存不受 JVM 直接管理。
实现类java.lang.ref.PhantomReference
// 必须配合引用队列使用
ReferenceQueueObject queue new ReferenceQueue();
PhantomReferenceObject phantomRef new PhantomReference(new Object(), queue);// 获取对象总是返回 null
Object obj phantomRef.get(); // 始终为 null// 监控回收通知
Reference? ref queue.remove(); // 阻塞直到有引用入队
if (ref phantomRef) {// 对象已被回收执行清理操作
}
虚引用和引用队列
虚引用必须和引用队列绑定创建虚引用时必须传入一个引用队列实例。对象回收时的通知机制当虚引用关联的对象被垃圾回收器判定为可回收时JVM 不会直接销毁这个对象而是先把对应的虚引用放入引用队列。程序的响应时机程序可以通过轮询引用队列一旦发现队列中有虚引用就知道关联的对象即将被回收此时可以执行后续操作如释放堆外内存。避免资源泄漏如果没有引用队列虚引用就无法通知程序对象被回收的事件导致程序无法及时处理堆外资源因为堆外内存不归 JVM 管理从而造成资源泄漏。
终结器引用 (Final Reference) 当对象没有强引用指向被判定为可回收时JVM 会先调用它finalizer方法若重写了该方法。这相当于让对象在 “被销毁前” 有机会执行一些清理操作比如
释放本地资源如 C 语言层面的句柄断开与外部资源的连接虽然不推荐但早期有程序这么用甚至通过重新建立强引用 “自救”避免被回收。
实现机制JVM 内部管理
// 创建引用队列
ReferenceQueueObject queue new ReferenceQueue();// 与引用配合使用
WeakReferenceObject ref new WeakReference(new Object(), queue);// 检查队列非阻塞
Reference? polledRef queue.poll();// 阻塞等待
try {Reference? removedRef queue.remove(); // 阻塞直到有引用入队
} catch (InterruptedException e) {Thread.currentThread().interrupt();
}
它的工作过程
当垃圾回收器检测到对象仅被终结器引用即没有其他强引用指向该对象时会将其放入终结队列Finalizer Queue。JVM 会启动一个低优先级的守护线程Finalizer 线程从队列中取出对象并调用其finalize()方法。执行完finalize()后对象在下一次 GC 时才会被真正回收。
但是在实际运用中不推荐
执行时机不可控JVM 何时调用finalize()完全不确定 —— 可能在对象可回收后很久才执行甚至因 JVM 退出而不执行导致资源长时间无法释放比如文件句柄占用。执行顺序混乱对象的finalize()执行顺序与创建顺序无关若多个对象存在依赖关系如 A 依赖 B可能出现 B 先被回收而 A 后回收的情况导致逻辑错误。性能开销大JVM 需要维护终结器引用队列处理finalize()方法的调用尤其当大量对象重写finalize()时会显著影响垃圾回收效率。 并且我们需要注意的是finalize方法仅仅会被执行一次。 在常规开发中虚引用和终结器引用不会使用了解即可。 软引用和弱引用可以不使用引用队列但是虚引用必须使用。 总结 堆内存是Java虚拟机中用于存储对象实例和数组的内存区域是内存管理的核心部分。其主要作用包括为对象实例分配内存空间所有new创建的对象都在堆中存储是垃圾回收的主要区域JVM通过垃圾回收机制自动管理堆中对象的生命周期回收不再被引用的对象以释放内存支持动态内存分配根据程序运行时的需求动态调整内存使用满足对象创建和销毁的动态变化。堆内存的大小可通过JVM参数如-Xms、-Xmx配置合理设置能优化内存使用和程序性能。
创作不易如果这篇文章对你有帮助请点赞收藏加转发感谢你的阅读你的支持就是我最大的动力。