HotSpot垃圾回收器
在垃圾回收语境下:
- 并行:多个垃圾回收线程并行工作,用户线程处于等待
- 并发: 垃圾回收线程与用户线程同时执行(不一定并行),两个线程运行在不同CPU上。
新生代收集器
Serial
单线程收集器,工作时会暂停所有工作线程,虽然会停顿时间较长但简单高效。client下默认的新生代收集器,因为桌面端内存不大,停顿时间很短。
ParNew
Serial的多线程版本,server下的首选新生代收集器,其中一个重要原因是它能跟CMS配合工作。
Parallel Scavenge
新生代收集器,停止复制算法,并行多线程,吞吐量优先。
吞吐量定义为CPU运行用户代码的时间占总时间的比例,以保证CPU能够运行尽可能多的用户代码。
该收集器设置了两个参数精准控制:
- 最大垃圾回收停顿时间:该值设定为一个大于0的毫秒数值,但该值小并不一定效率高,因为停顿时间的缩小是建立在牺牲新生代内存空间和吞吐量上的。由此可能引发更频繁的垃圾回收,从而带来效率下降。
- 吞吐量大小:代表垃圾回收时间占总时间的比例。该参数的值为大于0,小于100的整数,计算公式为1/(N+1),当填入99(默认)时,垃圾回收时间为1/100,即占用总时间的1%。
老年代收集器
Serial Old
Serial的老年代版本,单线程,标记-整理,client环境使用。在server端可以作为CMS的后备预案。
Parallel Old
Parallel Scavenge的老年代版本,多线程,标记-整理算法。
CMS
以最短GC停顿时间为目标,共分为四个步骤
- 初始标记
- 并发标记
- 重新标记
- 并发清理
- 初始标记仅仅标记GC Roots能直接关联到的对象,速度很快。
- 并发标记即为GC Roots追溯链,速度慢
- 重新标记即若并发标记期间如果有对象重新取得引用,将不再回收该对象
- 并发清理进行垃圾回收
CMS的缺点
- 占用线程资源:当CPU数量较少时会占用较多比例的线程资源处理垃圾回收,导致程序运行速度变慢。
- 无法处理浮动垃圾,可能导致Full GC:CMS是用用户线程一起并发进行的,在清理的过程中也会产生新的垃圾,这些垃圾需要下一次GC时清理,因此需要留出一部分内存使用,若内存不够用,将导致进行一次Full GC。因此该内存大小的设置需要合理。
- CMS采用标记-清理算法,会产生大量不连续碎片,当碎片空间无法创建大对象时不得不执行Full GC,因此改进当这种情况发生时进行碎片整理工作,整理工作无法并发,将导致停顿时间变长,同时增加了参数用于设置当连续进行多次不压缩Full GC后进行一次压缩Full GC。
G1收集器
G1优点
- 并发与并行:有效利用多核CPU
- 分代收集:用不同的方式处理新生代、老年代、持久代的对象
- 空间整合:整体基于标记-整理,局部为复制,不会产生内存碎片
- 可预测停顿:能让使用者指定在一个M毫秒的时间段内,消耗不超过N毫秒的时间来进行垃圾处理。
G1特点
将Java堆分成多个独立的Region区域,但仍保留新生、老年的划分。G1能够有效避免堆中的全区域垃圾回收,而是跟踪各个Region内的垃圾值,并进行排序,根据用户所允许的时间,选择最大的Region进行回收。
但对象在某个region,可能同时被其他Region的对象进行引用,从而造成全堆扫描,为了解决该问题,使用了Rememberd Set进行维护避免全堆扫描,当Reference进行写操作时会检查该对象是否存在其他Region里,若有则将信息添加到Rememberd Set中,当执行回收时,将Rememberd Set加入到追溯范围中,从而避免全堆扫描。
G1的操作步骤
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1步骤与CMS很多大致相同,最后的筛选回收阶段根据Region的排序列表和用户允许的时间进行筛选性回收。
GC日志解读
- 33.125:代表GC发送的时间,该时间以JVM启动以来经过的秒数
- [GC :代表的GC类型,如果是通过
System.gc()
手动调用,则会记录为[FULL GC(System) - [DefNew :代表GC区域,与收集器有关,这里的DefNew是Serial中的新生代的名称
- 3324K->152K(3712K):代表回收前区域已使用内存->回收后区域已使用内存(区域总内存)
- 0.0025925 secs:GC运行时间
- 3324K->152K(11904K):代表回收前堆已使用内存->回收后堆已使用内存(堆总内存)
GC类型
- Minor GC:对新生代进行垃圾回收
- Major GC:对老年代进行垃圾回收
- Full GC:对整个堆进行垃圾回收
各个回收不是绝对独立的,是相互有关联的。
对象内存分配
- 优先在新生代Eden区分配内存,如果内存不够则进行一次Minor GC,Minor GC过程中若Survivor容量不足,则存活对象将迁移至老年代中。
- 若对象过大(参数设定),则直接在老年代分配。
- 对象每经过一次Minor GC,年龄+1,当年龄达到15(默认,可更改)时将晋升至老年代。
- 若Survivor中年龄相同的对象占总容量的一半以上,所以大于等于该年龄的对象直接晋升至老年代。
空间分配担保
Minor GC时,需要将存活对象移至Survivor中,若Survivor容量不足,则使用老年代进行担保。所以为了安全,在进行Minor GC前先检查老年代的剩余容量是否大于此时新生代所有对象的容量(最坏情况GC后容量没变,全部存活),如果大于则此次Minor GC是安全的。如果不大于,则判断是否允许担保失败;如果允许失败,检查老年代的剩余空间是否大于以往晋升至老年代的平均对象大小,若大于,可以尝试进行有风险的Minor GC,若失败或不允许担保,将进行一次Full GC