03 GC 与内存分配

Wu Jun 2020-01-09 20:44:18
05 Java > 01 Java 虚拟机

1 GC判定

1.1 引用计数法

对象内部维护有一个被其他对象引用的引用计数,当这个引用计数为 0 的时候,表示对象可以被回收。
引用计数法存在一个问题——循环引用,加入 a 引用 b,b 同时也引用 a,那么就存在 ab 的引用计数都不为 0 的情况。

1.2 可达性分析算法

一个对象的根节点,不是 GC Roots 的话,就可以被回收。

image

Java 中可作为 GC Roots 的对象包括:

1.3 引用类型

Java 中有四种引用类型,默认是强引用类型:

1.4 生存还是死亡

在可达性分析算法中,如果没有与 GC Roots 引用链连接,会经历两次标记。

建议忘掉 finalize(),使用 try-finally

1.5 回收方法区(永久代)

主要回收两部分内容:废弃常量和无用的类

满足条件可以回收,不是必然回收

2 GC 算法

回收算法

分代收集

2.1 标记-清除

最基础的收集算法,其它算法都是对它不足的改进

1)两个阶段
  1. 标记(GC 判定)
  2. 清除
2)两点不足

image

2.2 复制算法

解决效率问题。

1)策略

将内存分为两块,每次都使用其中一块,当一块用完了,就将存活的对象复制到另一块,然后把使用过的一次清理掉。

代价是内存缩小为原来一半。

2)实现

用来回收新生代。新生代 98% 对象“朝生夕死”,所以不是 1:1 划分内存。

将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。回收时将存活的对象一次性复制到另一块 Survivor,清剩下的。

Hotspot 默认 Eden:Survivor:Survivor = 8:1:1,若回收时 Survivor 空间不够,通过老年代进行分配担保

image

2.3 标记-整理

老年代,应对 100% 对象存活

先标记可以被回收的对象,然后,让存活的对象向一端移动,最后直接清理掉另一端的内存。

image

2.4 分代收集算法

将内存根据生命周期分为几种,一般为新生代和老年代,然后根据特性,选择不同的回收算法。

3 算法实现

HotSpot的算法实现

3.1 枚举根节点

GC Roots 的节点主要是在全局性的引用(常量、类静态属性)与执行上下文(栈帧中的本地变量表)中,若逐个检查很耗时。

GC 进行时必须停顿所有 Java 执行线程(Stop The World)。

并不需要一个不漏地检查完所有引用位置,HotSpot 用 OopMap 来记录,GC 扫描时就直接知道了。

3.2 安全点

并非在所有地方都能停顿下来 GC,因为为每条指令都生成 OopMap 太浪费,只在特定安全点执行。

只有到达安全点才能暂停,GC。

有抢先式中断和主动式中断两种。通常用主动式中断

3.3 安全区域

被扩展的安全点,主要处理“不执行”的线程,Sleep 或 Blocked

4 垃圾收集器

4.1 新生代

1) 串行(Serial)

单线程,复制算法。Java 9 以前默认收集器。

2) 并行(ParNew)

Serial 的多线程版本。

3) 吞吐量优先(Parallel Scavenge)

多线程,复制算法,侧重于吞吐量的控制。

可以启用自适应调节策略,不用手工设置新生代大小等参数。

4.2 老年代

1) Serial Old

单线程,标记整理。Java 9 以前,Client 模式下默认收集器。

2) Parallel Old

多线程,标记整理,吞吐量优先

3) 并发标记清除(CMS)

并发收集、低停顿,“标记-清除”算法:

image

三个缺点:

4.3 新一代收集器

1) G1

Java 7 开始可以自主配置 G1 垃圾收集器,Java 9 开始作为默认的垃圾收集器,替代了之前默认使用的 Parallel GC

image

2) ZGC

【todo】Java 11 的新功能,承诺在数TB的堆上具有非常低的暂停时间。

4.4 理解 GC 日志

33.123: [GC [DefNew 3124K->123K(3333K), 0.0025925 secs] 2342K->122K(123232K), 0.34235 secs

4.5 垃圾收集器参数总结

1) 串行 GC 相关
2) 并行 GC 相关
3) CMS 相关
4) 与G1回收期相关的参数
5) TLAB相关
6) 其他参数

5 内存分配与回收策略

内存管理两个问题:分配内存、回收内存

分配就是在堆上分配,主要在新生代的 Eden 区上,少数情况也会直接分配在老年代中。

5.1 对象优先在 Eden 分配

Eden没有足够空间时会发起一次Minor GC

5.2 大对象直接进入老年代

大对象是只需要大量连续内存空间的对象(如长字符串、数组),应当避免“朝生夕灭”的短命大对象。

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

每个对象有个对象年龄计数器,当对象在 Eden 出生并经过第一次 Minor GC 后存活,并能容纳在 Survivor,就会被移到 Survivor。每度过一次 minor gc,岁数+1,当过了一定值(默认 15)的时候,就移入老年代

5.4 动态对象年龄判定

如果 survivor 中相同年龄的对象的大小达到总大小的一半,年龄大于或等于这个年龄的对象直接进入老生代,无需等到年龄门槛

5.5 空间分配担保

Minor GC 前看老年代剩余空间够不够装新生代失败的,不够就 FULL GC