Java GC 简述

106 阅读3分钟

内存分配和回收原则

1. 对象优先在Eden区分配

Eden区不够时,发起一次Minor GC(Young GC)

2. 大对象直接进入老年代

不同的垃圾回收器有不同的算法

3. 长期存活的对象将进入老年代

使用年龄计数器

大部分情况,对象都会首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。

此后,每进行一次MinorGC,年龄+1,到15(默认为15,最高也是15,但也分垃圾收集器),放到老年代

GC有几种

  • 部分收集(Partial GC):

  • 新生代收集(Minor GC/ Young GC):只对新生代进行垃圾收集;

  • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集

  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

  • 整堆收集 (Full GC):收集整个 Java 堆和方法区。

什么是空间分配担保

空间分配担保是为了确保在Minor GC之前老年代本身还有容纳新生代所有对象的剩余空间

死亡对象判断方法

引用计数法

有引用,计数+1,否则-1.

无法解决循环引用,因此不会用这种方法

可达性分析算法

通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

哪些对象可以作为Root?

  1. 虚拟机栈(栈帧中的局部变量表)中引用的对象

  2. 本地方法栈(Native 方法)中引用的对象

  3. 方法区中类静态属性引用的对象

  4. 方法区中常量引用的对象

  5. 所有被同步锁持有的对象

  6. JNI(Java Native Interface)引用的对象

被确认可以回收的对象还会经历两次标记,才会回收:

  1. 第一次会检查finalize方法是否有必要执行(没覆盖或者被调用过就没必要)

  2. 需要执行就会第二次标记,如果还是没有引用到Root引用链上,就回收

垃圾收集算法

标记-清除算法

最基础的算法,标记后,清除

复制算法

改进了标记清除,将内存分成大小相同的两块,每次回收完之后把还存活的对象复制到另一块,解决碎片问题。

导致可用内存变小、不适合老年代,存活对象数量大时,性能差。

标记-整理算法

标记完,将存活对象往一段移动,边界外的清理。

适合老年代,频率不高的场景

分代收集算法

不同代的回收用不同的策略。

比如老年代用标记整理,新生代用复制

垃圾收集器

  • Serial收集器:串行、新生代标记复制、老年代标记整理

  • ParNew: Serial的多线程版本

  • Parallel Scavenge: JDK1.8默认,与ParNew很像,更关注CPU执行效率,吞吐量

  • Serial Old: Serial老年代版本

  • Parallel Old: Parallel Scavenge老年代版本

  • CMS: 最短回收停顿。多线程标记清除,有三次标记,并发标记阶段会监听跟踪用户线程发生引用更新的地方

  • G1:高吞吐、短停顿。并发、标记整理,后台维护优先列表,根据允许的收集时间,优先选择回收价值最大的区域

  • ZGC:与ParNew和G1类似,牺牲了吞吐量,但暂停时间控制在几毫秒,且暂停时间不受堆内存大小影响

引用

文章参考了Java Guide JVM相关文章,做了整理和重点提取。

javaguide.cn/java/jvm/jv…