首页 / 资讯中心 / 文章详情

JVM垃圾回收机制与内存管理详解

JVM垃圾回收机制与内存管理详解 ★ FEATURED ARTICLE
1. JVM垃圾回收基础概念解析1.1 什么是JVM中的垃圾在Java虚拟机中垃圾Garbage的准确定义是堆内存和方法区中那些已经没有任何引用指向、程序在后续执行过程中永远无法再访问到的对象或数据所占用的内存空间。简单来说就是那些再也找不到的对象。1.1.1 垃圾产生的区域理解垃圾回收首先要明确哪些内存区域会产生垃圾内存区域是否产生垃圾是否需要GC原因说明程序计数器否否仅存储指令地址线程私有Java虚拟机栈否否栈帧随方法结束自动出栈本地方法栈否否同Java虚拟机栈Java堆是是所有对象实例的存储区域方法区(元空间)是是存储类信息、常量等直接内存间接否由操作系统管理从表中可以看出我们日常讨论的垃圾回收99%都是在处理Java堆中的对象垃圾。1.2 垃圾判定标准可达性分析JVM判断对象是否为垃圾的核心标准不是看是否被使用而是看是否可达。这里有两套判定算法1.2.1 引用计数法已弃用引用计数法是最直观的垃圾判定方法每个对象维护一个引用计数器当有引用指向它时计数器1引用失效时计数器-1计数器为0时判定为垃圾但这种方法存在致命缺陷无法处理循环引用的情况。例如class Node { Node next; } Node a new Node(); Node b new Node(); a.next b; b.next a; // 互相引用 a null; b null; // 虽然a和b都置为null但互相引用导致计数器不为0正因为这个缺陷HotSpot虚拟机完全弃用了引用计数法。1.2.2 可达性分析算法HotSpot采用这是JVM判断垃圾的官方标准也是面试必考内容。其核心思想是选定一组GC Roots根引用节点作为起点从GC Roots出发沿着引用链向下搜索能被搜索到的对象就是存活对象完全不在任何引用链上的对象就是不可达对象判定为垃圾GC Roots包括以下几类虚拟机栈中引用的对象局部变量、方法参数本地方法栈中JNI引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象所有被synchronized持有的对象1.3 堆中垃圾的常见形态在实际开发中垃圾对象通常有以下几种形态局部变量引用失效方法执行完毕后局部变量引用的对象引用主动置null显式将引用变量赋值为null临时对象如循环中创建的临时对象循环引用断开对象互相引用但没有外部引用1.4 方法区中的垃圾虽然方法区的垃圾回收较少但仍然存在两类垃圾废弃的常量字符串常量池中不再被引用的常量已卸载类的元数据满足类卸载三条件无实例、无Class引用、类加载器被回收2. JVM自动内存管理机制2.1 自动内存管理概述JVM的自动内存管理是Java区别于C/C的关键特性它包含两大核心功能自动内存分配为对象分配内存空间自动垃圾回收回收不再使用的对象内存2.1.1 堆的分代模型HotSpot虚拟机将堆划分为新生代和老年代基于分代假设绝大多数对象朝生夕死存活下来的对象会长期存在堆的具体划分区域占比特点GC频率新生代1/3Eden(80%)S0(10%)S1(10%)高老年代2/3单区域低2.2 内存分配机制2.2.1 对象分配流程新对象的内存分配遵循以下流程优先分配到Eden区Eden区满时触发Minor GC存活对象复制到Survivor区年龄1年龄达到阈值默认15晋升老年代大对象直接进入老年代2.2.2 内存分配算法JVM根据堆内存状态选择两种分配算法指针碰撞(Bump the Pointer)适用场景内存规整已用和空闲内存分明原理移动分界指针来分配内存特点速度快新生代常用空闲列表(Free List)适用场景内存不规整原理维护空闲内存块列表特点老年代常用2.3 垃圾回收机制2.3.1 GC核心步骤所有GC都遵循三个核心步骤标记通过可达性分析标记存活对象回收释放垃圾对象内存整理可选步骤解决内存碎片2.3.2 分代GC算法针对不同代采用不同算法算法适用区域优点缺点复制算法新生代速度快、无碎片浪费10%内存标记-清除老年代不浪费内存产生内存碎片标记-整理老年代无碎片、不浪费内存需要移动对象2.3.3 GC类型根据回收范围分为三种GCMinor GC回收新生代频繁但快速毫秒级使用复制算法Major GC回收老年代较少触发速度较慢Full GC回收整个堆和方法区速度慢秒级STW明显应避免手动触发System.gc()2.4 Stop The World现象GC执行时需要暂停所有用户线程称为STW(Stop The World)。不同GC的STW时间Minor GC毫秒级几乎无感知Full GC秒级明显卡顿STW是为了保证GC期间对象引用关系不被修改。3. 对象的finalization机制3.1 finalize()方法概述finalize()是Object类中定义的一个protected方法设计初衷是在对象被回收前释放其占用的本地资源如文件句柄、网络连接等。3.1.1 与C析构函数的区别重要区别析构函数必须执行时机确定finalize()不一定执行时机不确定3.2 finalize()执行流程第一次标记对象被判定为不可达放入F-Queue队列Finalizer线程执行finalize()第二次标记检查对象是否自救最终回收未自救的对象被回收3.3 对象自救示例public class RescueDemo { public static RescueDemo rescue; Override protected void finalize() throws Throwable { rescue this; // 自救 } public static void main(String[] args) throws Exception { rescue new RescueDemo(); rescue null; System.gc(); Thread.sleep(500); System.out.println(rescue ! null ? 自救成功 : 自救失败); } }3.4 finalize()的缺陷执行时机不可控仅执行一次增加GC负担可能导致对象意外存活无法处理异常由于这些问题Java 9已将其标记为DeprecatedJava 18直接移除。3.5 替代方案推荐使用以下方式替代finalize()try-finallyFileInputStream fis null; try { fis new FileInputStream(file.txt); // 使用资源 } finally { if (fis ! null) { fis.close(); } }try-with-resourcesJava 7try (FileInputStream fis new FileInputStream(file.txt)) { // 使用资源 } // 自动关闭4. 标记-清除算法详解4.1 算法原理标记-清除是最基础的GC算法分为两个阶段标记标记所有存活对象清除回收未标记的对象内存4.1.1 优点实现简单不浪费内存不移动对象4.1.2 缺点产生内存碎片效率不稳定4.2 适用场景主要适用于老年代因为老年代对象存活率高不适合频繁复制大对象5. 复制算法详解5.1 算法原理复制算法将内存分为两块每次只使用一块标记存活对象复制到另一块内存清空原内存块5.1.1 JVM优化实现HotSpot将新生代划分为Eden和两个Survivor区(8:1:1)将内存浪费从50%降到10%。5.2 执行流程Eden区满触发Minor GC标记Eden和Survivor区的存活对象复制到另一个Survivor区清空Eden和原Survivor区年龄达到阈值晋升老年代5.3 优缺点优点执行效率高无内存碎片分配简单缺点浪费10%内存不适合存活率高的区域6. 标记-整理算法详解6.1 算法原理标记-整理算法分为三个阶段标记同标记-清除整理将存活对象向一端移动清除回收端外内存6.2 适用场景主要适用于老年代因为无内存碎片不浪费内存适合存活率高的区域7. 分代收集算法7.1 算法思想分代收集不是新算法而是组合策略新生代复制算法老年代标记-清除或标记-整理7.2 分代假设基础弱分代假说绝大多数对象朝生夕死强分代假说熬过多次GC的对象更难消亡7.3 跨代引用处理使用记忆集(Remembered Set)记录跨代引用避免全堆扫描。8. 增量收集算法8.1 算法思想将GC工作分多次小增量完成减少单次STW时间。8.2 实现难点需要解决并发标记时的对象引用变化问题。9. 分区算法9.1 算法思想将堆划分为多个独立Region优先回收垃圾多的Region。9.2 G1收集器实现G1采用分区算法可预测停顿时间。10. 实际应用建议根据应用特点选择GC器吞吐量优先Parallel GC低延迟优先CMS或G1大堆应用G1或ZGC监控GC日志-XX:PrintGCDetails -XX:PrintGCDateStamps常见调优参数堆大小-Xms, -Xmx新生代比例-XX:NewRatioSurvivor区比例-XX:SurvivorRatio晋升阈值-XX:MaxTenuringThreshold避免内存泄漏注意静态集合的使用及时关闭资源避免不必要的对象持有理解JVM垃圾回收机制对于编写高性能Java应用至关重要。不同的GC算法适用于不同场景合理选择和调优可以显著提升应用性能。在实际开发中建议结合具体业务场景和性能测试结果来选择最合适的GC策略。
阅读完成 · 觉得有帮助?
咨询建站