JVM 垃圾回收机制

文章目录

  • JVM 垃圾回收机制
    • 概述
    • 垃圾标记阶段(判断对象存活)
      • 引用计数法
      • 可达性分析法
    • 垃圾回收阶段
      • 标记-清除算法
      • 复制算法
      • 标记-整理算法
      • 分代收集算法
    • 垃圾收集行为
    • Stop-the-World
    • 垃圾收集器类型

JVM 垃圾回收机制

概述

  • 在内存中已经不再被使用到的内存空间就是垃圾。

垃圾标记阶段(判断对象存活)

垃圾标记阶段用于判断对象是否可回收,只有被标记为已死亡的对象,GC才会在执行垃圾回收时,释放其占用的内存空间。

在Java虚拟机中,判断对象是否存活的两种方式:

  • 引用计数法
  • 可达性分析法

引用计数法

说明:

  • 在对象中添加一个引用计数器。
  • 当对象被引用+1,当对象失去引用-1;引用计数为0时,对象不再被引用,可以被回收。

优点:

  • 简单
  • 高效。

缺点:

  • 需要单独的字段存储计数器,增加了内存开销;每次赋值需要更新计数器,增加了时间开销。
  • 无法解决循环引用问题,目前在Java中几乎不使用这种算法。
objA.name = objB;
objB.name = objA;

可达性分析法

可达性分析法是以 GC Roots 为起点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链,当一个对象没有被任何引用链相连时,则证明该对象是不可用的,表示可以被回收。

JVM 垃圾回收机制_第1张图片

GC Roots 说明:

GC Roots 是一组活跃的引用。GC Roots 对象包含:

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 方法区中的静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象
  • 同步锁synchronized持有的对象

垃圾回收阶段

当区分出内存中存活对象和死亡对象后,GC接下来的任务是执行垃圾回收,释放掉死亡对象所占用的内存空间。

标记-清除算法

算法过程:

该算法分标记和清除两个阶段:

  • 标记阶段:标记所有需要回收的对象。
  • 清除阶段:统一回收所有被标记的对象。

JVM 垃圾回收机制_第2张图片

优点:

  • 算法简单、实现简单。

缺点:

  • 效率问题:标记和清除这两个过程效率不高。大部分对象都是朝生夕死的,因此需要大量标记对象和回收对象。
  • 空间问题:标记-清除后,会产生大量的内存碎片。内存碎片太多可能会导致需要分配较大对象时,无法找到足够的连续空间,从而提前触发另一次垃圾收集动作。

场景:

  • 标记-清除算法适合老年代。

复制算法

算法过程:

  • 将内存分为大小相等的两块,每次只使用其中一块。
  • 当这一块内存用完了,将这块内存中存活的对象复制到另一块中,然后一次清除使用的那块内存。

JVM 垃圾回收机制_第3张图片

优点:

  • 效率比标记-清除算法高。
  • 不会产生内存碎片问题。

缺点:

  • 可使用的内存缩小为原来的一半。
  • 当存活对象较多时,需要做多次复制操作,效率将变低。

场景:

  • 复制算法适合新生代。

标记-整理算法

与标记-清除算法类似,多了一个中间操作:整理内存。

标记-清除算法是一种非移动式的回收算法,标记-整理是移动式的。

算法过程:

  • 标记:标记存活对象。
  • 整理:让所有存活的对象向一端移动。
  • 清除:统一清除端以外的对象。

JVM 垃圾回收机制_第4张图片

优点:

  • 一次清除端外区域,比标记-清除算法中的清除效率高。
  • 不会产生内存碎片。

缺点:

  • 移动对象时会触发STW。

场景:

  • 标记-整理算法适合老年代。

分代收集算法

现在主流的虚拟机基本都采用 分代收集算法 ,即根据不同区域特点选择不同垃圾收集算法。

  • 根据对象存活周期不同,堆内存分为:新生代和老年代。
    • 新生代占1/3空间。
      • Eden区占8/10
      • From区占1/10
      • To区占1/10
    • 老年代占2/3空间。
  • 根据特点选择对应的垃圾收集算法。
    • 新生代:对象存活率低,垃圾回收行为频率高,采用复制算法,使用Minor GC。
    • 老年代:对象存活率搞,垃圾回收行为频率低,采用标记-整理和标记-清除算法,使用Full GC。

JVM 垃圾回收机制_第5张图片

算法过程:

JVM 垃圾回收机制_第6张图片

  1. 新创建的对象会先放在Eden区(位于新生代区,该区有大小限制),如果是大对象(如很长的字符串、数组)会直接分配到老年代区,避免频繁的内存复制。
  2. 如果Eden区的空间占满,程序又需要创建对象时,会触发MinorGC,将Eden区的存活对象移动到S0区并销毁垃圾对象,新创建的对象会放在Eden区,这是第一轮MinorGC后的操作。
  3. 当发生第二轮MinorGC后,会将存活Eden区和S0区的存活对象复制到S1区,并清除Eden区和S0区。
  4. 每次MinorGC存活对象的年龄都会加1,当存活对象的年龄达到阀值(默认为15),存活对象会移动到老年代区。
  5. 当老年代区内存不足时,会触发MajorGC,对老年代和新生代进行回收。当老年代内存仍不足时,会产生OOM异常。

优点:

  • 效率高。
  • 空间利用率高。

垃圾收集行为

  • Minor GC(小型垃圾收集):Minor GC主要清理新生代(Young Generation)的内存空间。新生代通常分为Eden区和两个Survivor区(S0和S1),大部分新创建的对象首先被分配到Eden区,当Eden区满时,就会触发Minor GC。
  • Major GC(大型垃圾收集):Major GC主要清理老年代(Old Generation)的内存空间。当对象在新生代存活一段时间后,或者Survivor区无法容纳的对象,会被移动到老年代。当老年代空间不足时,就会触发Major GC。Major GC的开销通常比Minor GC大,因为它涉及到整个堆的清理。
  • Full GC(全量垃圾收集):Full GC涉及到整个堆(包括新生代和老年代)以及方法区的清理。Full GC的开销最大,因为它需要暂停所有的应用线程(Stop-The-World)来进行垃圾收集。

Stop-the-World

Stop-the-World,简称STW,当 Full GC 发生时,会产生应用程序的停顿。停顿产生时整个应用程序都会被暂停,没有任何响应,像卡死一样,这个停顿称为STW。被STW中断的应用程序线程会在完成GC之后恢复。

垃圾收集器类型

查看垃圾收集器种类指令:java -XX:+PrintCommandLineFlags -version

收集器 收集对象和算法 集器类型 说明 适用场景
Serial 新生代,复制算法 单线程 简单高效;适合内存不大的情况;
ParNew 新生代,复制算法 并行的多线程收集器 ParNew垃圾收集器是Serial收集器的多线程版本 搭配CMS垃圾回收器的首选
Parallel Scavenge吞吐量优先收集器 新生代,复制算法 并行的多线程收集器 类似ParNew,更加关注吞吐量,达到一个可控制的吞吐量; 本身是Server级别多CPU机器上的默认GC方式,主要适合后台运算不需要太多交互的任务;
Serial Old 老年代,标记整理算法 单线程 Client模式下虚拟机使用
Parallel Old 老年代,标记整理算法 并行的多线程收集器 Parallel Scavenge收集器的老年代版本,为了配合Parallel Scavenge的面向吞吐量的特性而开发的对应组合; 在注重吞吐量以及CPU资源敏感的场合采用
CMS 老年代,标记清除算法 并行与并发收集器 尽可能的缩短垃圾收集时用户线程停止时间;缺点在于: 1.内存碎片 2.需要更多cpu资源 3.浮动垃圾问题,需要更大的堆空间 重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者B/S系统。互联网后端目前cms是主流的垃圾回收器;
G1 跨新生代和老年代;标记整理 + 化整为零 并行与并发收集器 JDK1.7才正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势; 面向服务端应用的垃圾回收器,目标为取代CMS

你可能感兴趣的:(#,Java,JVM系列,jvm,垃圾收集算法)