# 垃圾回收算法与垃圾回收器
# 一、学习垃圾回收的意义
Java
与 C++
等语言最大的技术区别:自动化的垃圾回收机制(GC
)。
为什么要了解 GC
和内存分配策略:
- 面试需要
GC
对应用的性能是有影响的;- 写代码有好处
栈中的生命周期是跟随线程,所以一般不需要关注。堆中的对象是垃圾回收的重点。
方法区/元空间,这一块也会发生垃圾回收,不过这块的效率比较低,一般不是我们关注的重点。
# 二、判断对象的存活
# 1、引用计数法
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。(Python
在用,但主流虚拟机没有使用)
优点:快,方便,实现简单。
缺陷:对象相互引用时(A.instance = B
,同时 B.instance = A
),很难判断对象是否该回收。
# 2、可达性分析(Java
中使用)
来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots
”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain
),当一个对象到 GC Roots
没有任何引用链相连时,则证明此对象是不可用的。
作为 GC Roots
的对象包括下面几种:
- 当前虚拟机栈中局部变量表中的引用的对象
- 当前本地方法栈中局部变量表中的引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
例如:
public class GCRoots {
Object o = new Object();
/**
* GC Roots
*/
static Object GC_Root_1 = new Object();
final static Object GC_ROOT_2 = new Object();
public static void main(String[] args) {
// 可达, = 不是赋值,在对象中是引用,传递的是右边对象的地址
Object object1 = GC_Root_1;
Object object2 = object1;
Object object3 = object1;
Object object4 = object3;
}
public void king() {
// 不可达(方法运行完后可回收),o 不是 GCRoots
Object object5 = o;
Object object6 = object5;
Object object7 = object5;
}
/**
* 本地变量表中引用的对象
*/
public void stack() {
// 本地变量表的对象
Object oStack = new Object();
// object8 在方法没有(运行完)出栈前都是可达的
Object object8 = oStack;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 3、忘记 finalize
finalize
可以完成对象的拯救,但是 JVM
不保证一定能执行,所以请忘记这个“坑”。
# 三、各种引用(Reference)
传统定义:Reference
中存储的数据代表的是另一块内存的起始地址。
# 1、强引用
一般的 Object obj = new Object()
,就属于强引用。
(如果有 GC Roots
的强引用)垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM
错误,使得程序异常停止。
# 2、软引用 SoftReference
垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它。
软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。
一些有用但是并非必需,用软引用关联的对象,系统将要发生 OOM
之前,这些对象就会被回收。参见代码:
public class TestSoftReference {
public static class User {
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
// new 是强引用
User u = new User(1, "King");
// 软引用通过强引用才能 new 出来
SoftReference<User> userSoft = new SoftReference<>(u);
// 干掉强引用,确保这个实例只有 userSoft 的软引用
u = null;
// 看一下这个对象是否还在
System.out.println(userSoft.get());
// 进行一次 GC 垃圾回收,千万不要写在业务代码中。
System.gc();
System.out.println("After gc");
System.out.println(userSoft.get());
// 往堆中填充数据,导致 OOM
List<byte[]> list = new LinkedList<>();
try {
for (int i = 0; i < 100; i++) {
System.out.println("------------" + userSoft.get());
// 1M 的对象
list.add(new byte[1024 * 1024 * 1]);
}
} catch (Throwable e) {
// 抛出了 OOM 异常时打印软引用对象
System.out.println("Exception ------------ " + userSoft.get());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
启动参数:-Xms10m -Xmx10m -XX:+PrintGC
,执行结果:
User [id=1, name=King]
[GC (System.gc()) 1739K->684K(9728K), 0.0008606 secs]
[Full GC (System.gc()) 684K->629K(9728K), 0.0049820 secs]
After gc
User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
------------User [id=1, name=King]
[GC (Allocation Failure) -- 7833K->7833K(9728K), 0.0023701 secs]
[Full GC (Ergonomics) 7833K->7793K(9728K), 0.0052514 secs]
[GC (Allocation Failure) -- 7793K->7793K(9728K), 0.0004019 secs]
[Full GC (Allocation Failure) 7793K->7775K(9728K), 0.0070479 secs]
Exception ------------ null
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
可以看出在进行了 GC
后,软引用被回收掉了。
例如,一个程序用来处理用户提供的图片。如果将所有图片读入内存,这样虽然可以很快的打开图片,但内存空间使用巨大,一些使用较少的图片浪费内存空间,需要手动从内存中移除。如果每次打开图片都从磁盘文件中读取到内存再显示出来,虽然内存占用较少,但一些经常使用的图片每次打开都要访问磁盘,代价巨大。这个时候就可以用软引用构建缓存。
# 3、弱引用 WeakReference
垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。
一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC
发生时,不管内存够不够,都会被回收。参看代码:
public class TestWeakReference {
public static class User {
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1, "King");
WeakReference<User> userWeak = new WeakReference<User>(u);
// 干掉强引用,确保这个实例只有 userWeak 的弱引用
u = null;
System.out.println(userWeak.get());
// 进行一次 GC 垃圾回收
System.gc();
System.out.println("After gc");
System.out.println(userWeak.get());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
执行结果:
User [id=1, name=King]
After gc
null
2
3
GC
过后,弱引用也被回收了。
注意:软引用 SoftReference
和弱引用 WeakReference
,可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。实际运用(WeakHashMap、ThreadLocal)
# 4、虚引用 PhantomReference
幽灵引用,最弱,被垃圾回收的时候收到一个通知。
如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。
# 四、GC(Garbage Collection)
看下面这个测试 OOM
的类:
public class Oom {
public static void main(String[] args) {
// 在方法执行的过程中,它是 GCRoots
List<Object> list = new LinkedList<>();
int i = 0;
while (true) {
i++;
if (i % 10000 == 0) {
System.out.println("i=" + i);
}
list.add(new Object());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
启动的参数:-Xms30m -Xmx30m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
,说明一下各个参数的含义:
-Xms: 堆区内存初始内存分配的大小
-Xmx: 堆区内存可被分配的最大上限
-XX:+PrintGCDetails:打印 GC
详情
-XX:+HeapDumpOnOutOfMemoryError:当堆内存空间溢出时输出堆的内存快照
-XX:SurvivorRatio:Eden
区和 2 个 Survivor
区每一个区的比值,8 表示 Eden : 两个 Survivor = 8 : 2,每个 Survivor
占 1/10。可以修改为 6,表示 Eden : 两个 Survivor = 6: 2 ,每个 Survivor
各站 1 / 10。
执行结果:
i=10000
i=20000
i=30000
i=40000
...
i=160000
[GC (Allocation Failure) [PSYoungGen: 8192K->1016K(9216K)] 8192K->7000K(29696K), 0.0247309 secs] [Times: user=0.14 sys=0.00, real=0.03 secs]
i=170000
...
i=370000
[GC (Allocation Failure) [PSYoungGen: 9208K->1000K(9216K)] 15192K->15248K(29696K), 0.0347185 secs] [Times: user=0.25 sys=0.02, real=0.03 secs]
[Full GC (Ergonomics) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 14248K->15132K(20480K)] 15248K->15132K(29696K), [Metaspace: 3214K->3214K(1056768K)], 0.3121589 secs] [Times: user=0.92 sys=0.00, real=0.31 secs]
i=380000
...
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20106K->20105K(20480K)] 28298K->28297K(29696K), [Metaspace: 3220K->3220K(1056768K)], 0.0979719 secs] [Times: user=0.94 sys=0.00, real=0.09 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20107K->20107K(20480K)] 28299K->28299K(29696K), [Metaspace: 3220K->3220K(1056768K)], 0.0952544 secs] [Times: user=1.09 sys=0.00, real=0.11 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20108K->20108K(20480K)] 28300K->28300K(29696K), [Metaspace: 3220K->3220K(1056768K)], 0.1010080 secs] [Times: user=0.94 sys=0.00, real=0.10 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20112K->20109K(20480K)] 28304K->28301K(29696K), [Metaspace: 3225K->3225K(1056768K)], 0.0996753 secs] [Times: user=0.94 sys=0.00, real=0.09 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20110K->20110K(20480K)] 28302K->28302K(29696K), [Metaspace: 3225K->3225K(1056768K)], 0.1038966 secs] [Times: user=1.09 sys=0.00, real=0.11 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20112K->20112K(20480K)] 28304K->28304K(29696K), [Metaspace: 3225K->3225K(1056768K)], 0.0988606 secs] [Times: user=0.94 sys=0.00, real=0.09 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20113K->20113K(20480K)] 28305K->28305K(29696K), [Metaspace: 3225K->3225K(1056768K)], 0.1003336 secs] [Times: user=1.09 sys=0.00, real=0.11 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->8192K(9216K)] [ParOldGen: 20118K->20118K(20480K)] 28310K->28310K(29696K), [Metaspace: 3225K->3225K(1056768K)], 0.1029586 secs] [Times: user=1.09 sys=0.00, real=0.10 secs]
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid18076.hprof ...
Heap dump file created [53895888 bytes in 0.136 secs]
[Full GC (Ergonomics) [PSYoungGen: 8192K->0K(9216K)] [ParOldGen: 20165K->637K(20480K)] 28357K->637K(29696K), [Metaspace: 3258K->3258K(1056768K)], 0.0050723 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
Heap
PSYoungGen total 9216K, used 224K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 2% used [0x00000000ff600000,0x00000000ff6381e8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 20480K, used 637K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
object space 20480K, 3% used [0x00000000fe200000,0x00000000fe29f5d8,0x00000000ff600000)
Metaspace used 3348K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 360K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.jerry.ch3.Oom.main(Oom.java:25)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
箭头左边的表示回收前的内存空间,右边是回收后的内存空间。
以 [GC (Allocation Failure)
开头的 GC
是 Minor GC
,主要对新生代进行回收。
以 [Full GC (Ergonomics)
开头的 GC
是 Full GC
,它对新生代、老年代、方法区都会进行回收。
可以看出越往后面回收的频次就越高,并且回收前后的内存空间变化都不大。
当 GC overhead limit exceeded
超过 98%的时间用来做 GC
并且回收了不到 2%的堆内存时会抛出此异常。
# 1、Minor GC
特点: 发生在新生代上,发生的较频繁,执行速度较快
触发条件: Eden 区空间不足\空间分配担保
# 2、Full GC
特点: 主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢
触发条件:调用 System.gc()
;老年代区域空间不足;空间分配担保失败
JDK 1.7
及以前的永久代(方法区)空间不足
CMS GC
处理浮动垃圾时,如果新生代空间不足,则采用空间分配担保机制,如果老年代空间不足,则触发 Full GC
# 五、垃圾回收算法
# 1、复制算法(Copying
)
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半。
示意图如下:
注意:内存移动是必须实打实的移动(复制),不能使用指针玩。
专门研究表明,新生代中的对象 98%是“朝生夕死”的,所以一般来说回收占据 10%的空间够用了,所以并不需要按照 1:1 的比例来划分内存空间,而是将内存分为一块较大的 Eden
空间和两块较小的 Survivor
空间,每次使用 Eden
和其中一块 Survivor[1]
。当回收时,将 Eden
和 Survivor
中还存活着的对象一次性地复制到另外一块 Survivor
空间上,最后清理掉 Eden
和刚才用过的 Survivor
空间。
HotSpot VM
默认 Eden
和 Survivor
的大小比例是 8:1,也就是每次新生代中可用内存空间为整个新生代容量的 90%(80%+10%),只有 10%的内存会被“浪费”。
# 2、标记-清除算法(Mark-Sweep
)
过程:
- 首先标记所有需要回收的对象
- 统一回收被标记的对象
缺点:
- 效率问题,标记和清除效率都不高
- 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
示意图如下:
# 3、标记-整理算法(Mark-Compact
)
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
示意图如下:
# 六、垃圾回收器
# 1、分代收集
根据各个年代的特点选取不同的垃圾收集算法。新生代使用复制算法,老年代使用标记-整理或者标记-清除算法
使用 jps -v
显示当前使用的垃圾回收器,在我的电脑上使用这个指令会有这么一段内容:-XX:+UseConcMarkSweepGC
,说明使用的是标记-清除算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
收集器 | 收集对象和算法 | 收集器类型 |
---|---|---|
Serial | 新生代,复制算法 | 单线程 |
ParNew | 新生代,复制算法 | 并行的多线程收集器 |
Parallel Scavenge | 新生代,复制算法 | 并行的多线程收集器 |
Serial Old | 老年代,标记整理算法 | 单线程 |
Parallel Old | 老年代,标记整理算法 | 并行的多线程收集器 |
CMS | 老年代,标记清除算法 | 并行与并发收集器 |
G1 | 跨新生代和老年代;标记整理 + 化整为零 | 并行与并发收集器 |
这里的并行和并发的含义如下:
- 并行
垃圾收集的多线程的同时进行。
- 并发
垃圾收集的多线程和应用的多线程同时进行
单线程收集和多线程并行收集的示意图:
注:吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
垃圾收集时间 = 垃圾回收频率 * 单次垃圾回收时间
# 2、各种垃圾回收器
# (1) Serial/Serial Old
最古老的,单线程,独占式,成熟,适合单 CPU 服务器
-XX:+UseSerialGC
新生代和老年代都用串行收集器
-XX:+UseParNewGC
新生代使用 ParNew
,老年代使用 Serial Old
-XX:+UseParallelGC
新生代使用 ParallerGC
,老年代使用 Serial Old
# (2) ParNew
和 Serial
基本没区别,唯一的区别:多线程,多 CPU 的,停顿时间比 Serial
少
-XX:+UseParNewGC
新生代使用 ParNew
,老年代使用 Serial Old
除了性能原因外,主要是因为除了 Serial
收集器,只有它能与 CMS
收集器配合工作。
# (3) Parallel Scavenge(ParallerGC)/Parallel Old
关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是 99%。
# (4) Concurrent Mark Sweep (CMS)
收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java
应用集中在互联网站或者 B/S
系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS
收集器就非常符合这类应用的需求。
-XX:+UseConcMarkSweepGC
,一般新生代使用 ParNew
,老年代的用 CMS
从名字(包含“Mark Sweep”)上就可以看出,CMS
收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,
CMS
的回收过程如下,分为4个步骤:
初始标记:仅仅只是标记一下
GC Roots
能直接关联到的对象,速度很快,需要停顿(STW
-Stop the world
)。并发标记:从
GC Root
开始对堆中对象进行可达性分析,找到存活对象,它在整个回收过程中耗时最长,不需要停顿。重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(
STW
)。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。并发清除:不需要停顿。
- 优点
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS
收集器的内存回收过程是与用户线程一起并发执行的。
- 缺点
CPU 资源敏感:因为并发阶段多线程占据 CPU
资源,如果 CPU
资源不足,效率会明显降低。
浮动垃圾:由于 CMS
并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留待下一次 GC
时再清理掉。这一部分垃圾就称为“浮动垃圾”。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS
收集不能像其它收集器那样等待老年代快满的时候再回收。在 1.6 的版本中老年代空间使用率阈值(92%)如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure
,这时虚拟机将临时启用 Serial Old
来替代 CMS
。
会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片
# (5) G1 垃圾回收器
-XX:+UseG1GC
使用 G1
垃圾回收器。
# 内部布局改变
G1
把堆划分成多个大小相等的独立区域(Region
),新生代和老年代不再物理隔离。
算法:标记—整理(humongous
)和复制回收算法(survivor
)。
# GC 模式
- Young GC
选定所有年轻代里的 Region
。通过控制年轻代的 region
个数,即年轻代内存大小,来控制 Young GC
的时间开销。(复制回收算法)
- Mixed GC
选定所有年轻代里的 Region
,外加根据 global concurrent marking
统计得出收集收益高的若干老年代 Region
。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region
。
Mixed GC
不是 full GC
,它只能回收部分老年代的 Region
。如果 mixed GC
实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行 Mixed GC
,就会使用 serial old GC
(full GC
)来收集整个 GC heap
。所以我们可以知道,G1
是不提供 full GC
的。
# 回收过程-全局并发标记(global concurrent marking
)
- 初始标记
仅仅只是标记一下 GC Roots
能直接关联到的对象,并且修改 TAMS
(Nest Top Mark Start
)的值,让下一阶段用户程序并发运行时,能在正确可以的 Region
中创建对象,此阶段需要停顿线程(STW
),但耗时很短。
- 并发标记
从 GC Root
开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
- 最终标记
为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs
里面,最终标记阶段需要把 Remembered Set Logs
的数据合并到 Remembered Set
中。这阶段需要停顿线程(STW
),但是可并行执行。
- 筛选回收
首先对各个 Region
中的回收价值和成本进行排序,根据用户所期望的 GC
停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region
,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
# 特点
- 空间整合
不会产生内存碎片
- 算法
标记—整理(humongous
)和复制回收算法(survivor
)。
- 可预测的停顿
G1
收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java
堆中进行全区域的垃圾收集。G1
跟踪各个 Region
里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region
(这也就是 Garbage-First
名称的来由)。这种使用 Region
划分内存空间以及有优先级的区域回收方式,保证了 G1
收集器在有限的时间内可以获取尽可能高的收集效率。
# G1 GC 主要的参数
参数 | 含义 |
---|---|
-XX:G1HeapRegionSize=n | 设置 Region 大小,并非最终值 |
-XX:MaxGCPauseMillis | 设置 G1 收集过程目标时间,默认值 200ms,不是硬性条件 |
-XX:G1NewSizePercent | 新生代最小值,默认值 5% |
-XX:G1MaxNewSizePercent | 新生代最大值,默认值 60% |
-XX:ParallelGCThreads | STW 期间,并行 GC 线程数 |
-XX:ConcGCThreads=n | 并发标记阶段,并行执行的线程数 |
-XX:InitiatingHeapOccupancyPercent | 设置触发标记周期的 Java 堆占用率阈值。默认值是 45%。这里的 Java 堆占比指的是 non_young_capacity_bytes ,包括 old+humongous |
# 3、垃圾回收器的重要参数(使用-XX:)
参数 | 描述 |
---|---|
UseSerialGC | 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 |
SurvivorRatio | 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄,每个对象在坚持过一次 Minor GC 之后,年龄就增加 1,当超过这个参数值时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行 GC 时进行内存回收的线程数 |
GCTimeRatio | GC 时间占总时间的比率,默认值为 99,即允许 1% 的 GC 时间,仅在使用 Parallel Scavenge 收集器生效 |
MaxGCPauseMillis | 设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效 |
CMSInitiatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值为 68%,仅在使用 CMS 收集器时生效 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效 |
# 4、STW(Stop The World) 现象
GC
收集器和我们 GC
调优的目标就是尽可能的减少 STW
的时间和次数。