JVM笔记六:垃圾收集器与内存分配策略——垃圾收集器

文章目录
  1. 1. Serial收集器
  2. 2. ParNew收集器
  3. 3. Parallel Scavenge收集器
  4. 4. Serial Old收集器
  5. 5. Parallel Old收集器
  6. 6. CMS收集器
  7. 7. G1收集器
  8. 8. 垃圾收集器参数总结

基于JDK1.7 Update 14之后的HotSpot虚拟机的垃圾收集器。

两个收集器之间有连线,说明他们可以搭配使用。收集器所处的区域,则表示他是属于新生代收集器还是老生代收集器。

Serial收集器

Serial / Serial Old收集器运行示意图

新生代收集(Minor GC)

下面代码尝试分配3个2MB大小和1个4MB大小的对象

  • 运行通过-Xms20M(堆初始大小)、-Xmx20M(堆最大值)、-Xmn10M(新生代值)这3个参数限制了Java堆大小为20MB,不可扩展,其中10MB分配给新生代,剩下的10MB分配给老年代
  • -XX:SurvivorRatio=8 决定了新生代中Eden区与一个Survivor区的空间比例为 8:1
  • 新生代总可用空间:9216KB(Eden区+1个Survivor区的总容量)
  • -XX:+PrintGCDetail,在发生垃圾收集时打印内存回收日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* MinorGC
* VM Args: -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* Created by larry.su on 2017/2/21.
*/

public class MinorGC {
public static final int _1M = 1024 * 1024;

public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[2 * _1M];
allocation2 = new byte[2 * _1M];
allocation3 = new byte[2 * _1M];
allocation4 = new byte[4 * _1M]; //出现一次MinorGC
}
}
  1. main方法执行后,在分配allocation4对象时,发生一次Minor GC,这次GC的结果是新生代7814K变为423K,而总内存占用量(7814K->6567K(19456K))几乎没有减少(因为allocation1、allocation2、allocation3三个对象都是存活的,虚拟机几乎没有找到可回收的对象)。

  2. 这次GC的原因是给allocation4分配所需的4MB内存的时候,发现Eden已经被占用了6MB,剩余空间已不足分配allocation4对象所需的4MB,因此发生Minor GC。

  3. GC期间虚拟机又发现已有的3个2MB的对象无法全部放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。

  4. 这次GC结束后,4MB的allocation4对象顺利分配在Eden中,因此程序执行完成的结果是Eden占用4MB,Servivor空闲,老年代被占用6MB。

JDK1.6使用Serial收集器的GC日志:

1
2
3
4
5
6
7
8
9
10
[GC (Allocation Failure) [DefNew: 7814K->423K(9216K), 0.0051201 secs] 7814K->6567K(19456K), 0.0051387 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4656K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf022408, 0x00000007bf400000)
from space 1024K, 41% used [0x00000007bf500000, 0x00000007bf569ca8, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
Metaspace used 2917K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 318K, capacity 386K, committed 512K, reserved 1048576K

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,使用多条线程进行垃圾收集。

ParNew / Serial Old收集器运行示意图

MinorGC示例使用ParNew收集器的GC日志:

JDK8虚拟机参数:

  • -Xms20M
  • -Xmx20M
  • -Xmn10M
  • -XX:+PrintGCDetails
  • -XX:+UseParNewGC
1
2
3
4
5
6
7
8
9
10
[GC (Allocation Failure) [ParNew: 7814K->467K(9216K), 0.0056367 secs] 7814K->6611K(19456K), 0.0056588 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 4700K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf022408, 0x00000007bf400000)
from space 1024K, 45% used [0x00000007bf500000, 0x00000007bf574ed8, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
Metaspace used 2946K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 319K, capacity 386K, committed 512K, reserved 1048576K

后面还会接触几款并行和并发的收集器。在谈垃圾收集的上下文语境下,可以解释如下:

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个cpu上

Parallel Scavenge收集器

Serial Old收集器

Serial / Serial Old收集器运行示意图

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

MinorGC示例使用Parallel Scavenge + Parallel Old收集器的GC日志:

JDK8虚拟机参数:

  • -Xms20M
  • -Xmx20M
  • -Xmn10M
  • -XX:+PrintGCDetails
  • -XX:+UseParallelOldGC
1
2
3
4
5
6
7
8
9
Heap
PSYoungGen total 9216K, used 7979K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 97% used [0x00000007bf600000,0x00000007bfdcac90,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 10240K, used 4096K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 40% used [0x00000007bec00000,0x00000007bf000010,0x00000007bf600000)
Metaspace used 2967K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 322K, capacity 386K, committed 512K, reserved 1048576K

『ParOldGen total 10240K, used 4096K』看出allocation4直接被分配到老年代中。

CMS收集器

Concurrent Mark Sweep 收集器运行示意图

MinorGC示例使用ParNew + CMS + Serial Old(后备)收集器的GC日志:

JDK8虚拟机参数:

  • -Xms20M
  • -Xmx20M
  • -Xmn10M
  • -XX:+PrintGCDetails
  • -XX:+UseConcMarkSweepGC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[GC (Allocation Failure) [ParNew: 8144K->455K(9216K), 0.0054356 secs] 8144K->6601K(19456K), 0.0054911 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6146K(10240K)] 10697K(19456K), 0.0002332 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Final Remark) [YG occupancy: 4551 K (9216 K)][Rescan (parallel) , 0.0002916 secs][weak refs processing, 0.0000330 secs][class unloading, 0.0001571 secs][scrub symbol table, 0.0003754 secs][scrub string table, 0.0001245 secs][1 CMS-remark: 6146K(10240K)] 10697K(19456K), 0.0010223 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 4634K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000)
from space 1024K, 44% used [0x00000007bf500000, 0x00000007bf571f58, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
concurrent mark-sweep generation total 10240K, used 6146K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2811K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 305K, capacity 386K, committed 512K, reserved 1048576K

新生代使用的ParNew收集器,allocation4分配时新生代已没有足够的空间分配。Survivior只有1M不能够存放allocation1,2,3三个对象,所以这三个对象会复制到concurrent mark-sweep generation 中。最终allocation4分配到par new generation中。

从GC日志可以看出老年代GC的步骤:CMS-initial-mark -> CMS-concurrent-mark -> CMS-remark -> CMS-concurrent-sweep -> CMS-concurrent-reset(重置状态等待下一次触发GC)

G1收集器

G1收集器运行示意图

垃圾收集器参数总结

垃圾收集相关的常用参数:

参数 描述
UseSerialGC 虚拟机运行在Client模式下的默认值,打开此开关后,使用 Serial + Serial Old的收集器组合进行内存回收
UseParNewGC 打开此开关后,使用ParNew + Serial Old的收集器组合进行内存回收
UseConcMarkSweepGC 打开此开关后,使用ParNew + CMS + Serial Old的收集器组合进行内存回收。Serial Old作为CMS出现Concurrent Mode Failure失败后的后备收集器使用
UseParallelGC 虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scanvenge + 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收集器时生效