https://downloads.plumbr.io/java%20lang%20OutOfMemoryError%20Handbook%20-%20Plumbr.pdf
垃圾回收的实现
事实是,虚拟机一般需要2种GC分别用于清理:新生代和老年期
我可以指定,如果不指定也会有默认的
虽然图中有很多, 事实上只用加粗的4种
- 1.都用Serial
- 2.都用Parallel
- 3.Parallel 和 CMS
- 4.都用G1
Concurrent Mark and Sweep
老年代的收集器(和ParNew配合), 并发 标记-清除
目标是避免老年代收集时的长停顿,
这靠2个措施:
- 1.标记以后不压缩. 只用free-lists来管理收回的空间
- 2.标记-清除的大多数工作 和 应用 同时进行, 这意味着 不是完全停止应用线程, 用多线程来完成收集
默认用于gc的线程为 (cpu核数+3)/4
因为和应用并行,不能等满了,应用没内存用了才发起,而是老年代内存占用达到68%(可调)就要发起
使用示例
java -XX:+UseConcMarkSweepGC com.mypackages.MyExecutableClass
看 全GC的日志
2015-05-26T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [1 CMS-initial-mark:
10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00,
real=0.00 secs]
2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times:
user=0.07 sys=0.00, real=0.03 secs]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs]
[Times: user=0.02 sys=0.00, real=0.02 secs]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074
secs] [Times: user=0.20 sys=0.00, real=1.07 secs]
2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K
(613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: [weak refs processing,
0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: [scrub symbol table,
0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark:
10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [Times: user=0.06 sys=0.00,
real=0.01 secs]
2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] [Times:
user=0.03 sys=0.00, real=0.03 secs]
2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [Times:
user=0.01 sys=0.00, real=0.01 secs]
和前2个收集器不同, CMS 是分阶段的
也可能会 并发收集失败 Concurrent Mode Failure, 这是因为老年代空间不足(老是这样就需要调大老年代, 或者调小触发阈值)
如果失败,就会用最古老最慢的Serial Old 来收集, 这就慢了
1: Initial Mark 初始标记(STW)
这是唯二的需要 stop-the-world 的阶段
这阶段目的是 标记 根对象, 就是GC roots能直接关联到的对象, 很快就能完成
1.2015-05-26T16:23:07.321-0200 发生的时刻, 64.425 JVM 运行了这时长时发生
3.4.老年代 现在占的内存 总可用内存
5.6.堆 现在占的内存. 总可用内存
7.所用的时间
2:Concurrent Mark 并发标记
如名字所说,标记所有对象, 和应用同时进行,应用不用停
从上一个阶段得到的 根对象 出发, 在老年代标记 所有活着的对象
3.这个所花的时间值, 没有多大的意义, 因为这段时间是和应用一起的,不止是垃圾回收线程在干活
3: Concurrent Preclean 并发预清理
也是和应用同时地进行的阶段
上一个阶段标记的一些有了变化, 有字段变化的对象被标记为dirty(这叫Card Marking),上一个阶段标记为活的可能有错,实际上死了,但是重要的是 活的没被认为是死的. 这个阶段也为最终标记阶段做准备
4: Concurrent Abortable Preclean 可中止的并发预清理
也是不用停应用的, 为了减轻下一个停应用的阶段(最终重标记)的负担
这个阶段的时间有很多影响因素,因为迭代做很多事,直到遇到停止条件(如:迭代次数,完成的有用工作量,经过的时间等)
2.cpu时间/时钟时间(真的过去的时间):
user/real
用户时间user,就是cpu时间,
真实时间real,就时钟时间,在现实世界过去的世界
值得注意的是,user比real小很多,
这是不正常的,
一般来说,real会比user少,这是因为工作可多线程并发来干, 比较快.
这里, user只有0.167,做了一点点工作, 垃圾收集线程们只是等了几乎一秒,没干啥
因为下一个阶段是STW的, 本阶段会特点等一次小GC,根据历史性能数据 预测下一次小GC的时间, 让下一个阶段和小GC 不连着发现(尽量让下一个阶段开始在2次小GC的中点), 如果连着的话 应用就一次停太久了
5: Final Remark 最终重新标记(STW)
停应用. 目标是最终标记老年代 所有 活着的线程
加上脏对象(并行阶段被改的)
这个阶段比初始标记的阶段会时间长一点, 但是远比并发标记短
CMS尽量在新生代空的时候执行这个节点, 为了避免连续 stop-the-world
4.多线程的重新扫描活着的对象, 当应用停止, 耗时这些秒
5.子阶段:处理弱引用的时间长和时刻
6.子阶段:卸载不用的类,用的时长和发生的时刻
7.子阶段:清朝标志(类级别的元信息)和字符串表(内化的字符串)
10.耗时
5个阶段用户,所有老年代活的对象都已经被标记,可以开始清除老年代了
6: Concurrent Sweep 并发清理
不停应用
目的是,移除没用的对象,重新声明他们占的空间以后可以用了
7: Concurrent Reset. 并发重置
重置内部数据结构,为下一次轮回准备
总结
CMS依靠把很多工作用并发线程(不停应用)们来执行,来避免停顿时间
缺点是,
1.标记清除的缺点,就是老年代的碎片
可以开启每次都碎片整理(STW),还可以设定多少次以后 内存压缩
2.区分停止时间的预见性,特别是堆大的时候
还是G1好
网友评论