Java垃圾回收
内存分配原则
对象优先在Eden区分配
- 新生代划分: 新生代通常分为Eden区和两个Survivor区。新创建的对象通常优先在Eden区分配内存。
- 快速回收: 由于大多数对象是短命的,Eden区的回收效率较高。
大对象直接进入老年代
- 避免复制开销: 大对象会导致在新生代中的复制成本过高,因此大对象通常直接在老年代分配。
- 垃圾回收器的优化: 有些垃圾回收器,如G1垃圾回收器,优化了对大对象的处理。
长期存活的对象进入老年代
- 年龄计数: 对象在新生代中经历每次Minor GC时,其年龄增加。当对象年龄达到某个阈值(默认15),就会被移动到老年代。
动态对象年龄判定
- 自适应的年龄阈值: JVM可以根据新生代Survivor区的实际使用情况,动态调整对象晋升到老年代的年龄阈值,优化内存管理。
内存回收原则
分代回收
- 新生代GC(Minor GC): 频繁进行,回收新生代的短命对象。通常采用复制算法。
- 老年代GC(Major GC 或 Full GC): 较少进行,回收老年代的对象。回收速度较慢,通常采用标记-整理或标记-清除算法。
空间分配担保
- 担保机制: 在进行Minor GC时,如果发现Survivor区无法容纳所有Eden区和当前Survivor区的存活对象,就需要通过空间担保将这些对象放入老年代。若老年代无法承受,就会触发一次Full GC。
垃圾回收器选择
- 不同垃圾回收器: 不同的垃圾回收器如Serial、Parallel、CMS、G1等有不同的特点,适用于不同的应用场景。选择合适的垃圾回收器可以优化内存使用和应用程序的性能。
适时的Full GC
- 触发条件: Full GC会清理整个堆,包括新生代和老年代。它通常在老年代空间不足时触发,但也可能因为系统调用或其他因素触发。
- 性能影响: Full GC通常是性能开销较大的操作,尽量减少其发生频率是优化系统性能的关键。
死亡对象判断方法
在JVM中,判断一个对象是否“死亡”(即不再被使用)是垃圾回收的关键步骤。
引用计数法
概念: 每个对象有一个引用计数器,当有一个新的引用指向该对象时,引用计数器加1;当一个引用不再指向该对象时,引用计数器减1。
判断死亡: 如果一个对象的引用计数器为零,说明该对象没有被任何引用引用,可以认为是死亡对象,能够被垃圾回收。
优点: 实现简单,垃圾回收器能够立即回收引用计数为零的对象。
缺点: 无法处理循环引用的问题。例如,两个对象相互引用,但没有其他外部引用指向它们,这种情况下引用计数不会变为零,导致内存泄漏。
可达性分析
概念: 使用一组称为“根对象”(GC Roots)的引用作为起点,从这些根对象开始遍历对象图,所有能从根对象到达的对象被认为是“存活”的,而不能到达的对象被认为是“死亡”的。
GC Roots包括:
- 虚拟机栈中的引用对象(如局部变量、方法参数等)
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
判断死亡: 如果在从GC Roots出发的搜索过程中,没有到达某个对象,则该对象被视为不可达,可以 被认为是死亡对象,垃圾回收器可以回收其占用的内存。
优点: 能够准确处理对象之间复杂的引用关系,包括循环引用。
缺点: 需要遍历对象图,计算量较大,性能开销高于引用计数法。
垃圾回收算法
标记-清除算法
标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
- 缺点: 会导致内存碎片化,影响后续内存分配效率。
复制算法
将堆分为两块,称为"From"和"To"空间。每次垃圾回收时,将存活的对象从"From"空间复制到"To"空间,然后清空"From"空间。
- 缺点: 需要两倍的内存空间,并且适合对象存活率低的场景,如新生代。