JVM的垃圾回收

一、JVM的垃圾回收算法

垃圾回收机制是JVM的一个重要机制,它可以自动帮助开发者清除已弃用的对象,达到内存回收的目的。而不同的内存需要采用不同的垃圾回收算法,确保兼顾效率和回收率。
常见的垃圾回收算法有以下四种:

  • 标记-清除算法
    • 标记阶段:从一组GC Roots对象出发,遍历所有的可达对象,并标记为“存活”。
    • 清除阶段:遍历堆内存,将未标记为存活的对象清除,从而释放内存。
  • 复制算法
    • 将内存分为FromTo两块区域,对象创建在From区域,触发垃圾回收机制时,JVM会将From区域的所有存活对象复制到To区域,并清除From区域的所有空间,最后再调换FromTo区域。从而达到内存回收的目的。
  • 标记-整理算法
    • 标记阶段:与标记-清除算法相同。
    • 整理阶段:将存活的对象移到内存的一端,并清除剩余的空间。
  • 分代GC算法
    将对象分为新生代老年代
    • 新生代:分为Eden区Survivor区 (Survivor0和Survivor1)。新对象创建一般在Eden区,当Eden区内存不足时,会触发Minor GC ,在Survivor区采用复制算法回收内存。
    • 当对象为大对象/在新生代经历多次GC仍存活时,对象就会存储在老年代。老年代一般采用标记-清除算法或标记-整理算法。

二、如何判断对象是否存活

  • 引用计数法
    机制 :对每个对象的被引用数 进行统计,当有新的引用指向该对象时,引用计数+1;反之-1。如果引用数为0,我们可以初步判断该对象已经死亡。
    缺点 :当两个死亡对象出现循环引用时,引用计数不为0,但是仍需要回收。
  • 可达性分析法
    通过一组GC Roots对象遍历所有可达对象,遍历后标记的便为存活对象。
    • GC Roots对象有:
      • JVM中栈的引用(方法的局部变量、参数等)
      • 静态变量引用
      • 运行时常量池中的引用
      • JNI本地方法引用
    • 对象引用类型:
      • 强引用:new 创建的对象
      • 弱引用:在内存不足时会回收,用于缓存
      • 软引用:只要触发GC就会回收
      • 虚引用:无法通过虚引用访问对象,主要用于跟踪对象的存活状态
    • 对象的生存状态:
      • 可达:对象能被GC Roots访问
      • 可回收(第一次标记):对象不可达,但是可能复活(finalize()方法中重新引用GC Roots)
      • 不可复活(第二次标记):finalize()之后仍不可达,则认定为垃圾

三、JVM中的垃圾回收器

常用的垃圾回收器为G1CMS 回收器:

  • CMS回收器 ,以最小化停顿时间为目标,适用于高响应 的服务(如Web应用),基于标记-清除 算法,清理流程如下:
    CMS回收器流程图
    1. 停止所有用户线程(STW),启动一个GC线程 进行初始标记 ,遍历可达对象。
    2. 用户线程和GC线程进行并发标记 ,分析对象存活情况。
    3. 停止所有用户线程,启动多个GC线程 进行最终标记 ,修正并发标记过程中产生的变动
    4. 启动多个用户线程和一个GC线程 ,进行并发清除 ,完成后重置GC线程。
      优点响应快停顿时间短
      缺点 :标记清除算法会产生内存碎片 ;并发标记过程用户线程 产生的垃圾需要下次才能回收
  • G1回收器 :以控制GC停顿时间为目标,兼具高吞吐量和低延迟性能,适用于大内存、多核环境。基于标记-整理和标记-复制算法,回收过程如下:
    G1垃圾回收器示意图
    1. 停止所有用户线程(STW),启动一个GC线程 进行初始标记,遍历可达对象。
    2. 用户线程和一个GC线程 进行二次标记 ,分析对象存活情况。
    3. 停止所有用户线程(STW),启动多个GC线程 进行并发最终标记 ,修正标记过程中产生的引用变动。
    4. 让多个GC线程进行筛选回收 ,根据收集时间预算,优先回收回收价值最高的区域 ,回收完之后对GC线程进行重置。