跳到主要内容

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"空间。

  • 缺点: 需要两倍的内存空间,并且适合对象存活率低的场景,如新生代。

标记-整理算法

标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

  • 缺点: 需要移动对象,效率相对较低。

分代收集算法

根据对象的存活时间,将堆划分为不同的代(Generation),通常为新生代(Young Generation)和老年代(Old Generation)。

新生代中对象存活时间短,使用复制算法进行回收;老年代中对象存活时间长,使用标记-清除或标记-整理算法进行回收。