从GC日志说起(一)

学习JVM,或许你已经看了一大堆概念:新生代、老年代、GC、CMS...,可能你已经背的滚瓜烂熟,牢记于心,只等谁来考你。
但是,知识,永远只是知识;文字,只能是文字。我们人类的最佳理解方式是经验,只有我们经验到了,才能叫我们掌握了这个知识,看懂了这段文字。也就是所谓的知行合一。

那么,怎么做到知行合一呢?
在学习编程的过程中,对一个概念有了感性的认识就可以叫知行合一。
什么叫感性的认识呢?
比如,甜味,你尝一下糖,就算感性认识到了什么叫甜。同理,你看一下GC日志,就可以感性认识到JVM的GC过程。

软件就是这么个东西,全都不是实物,很难感性认识到,所以也就很难学习。每次过年回家我都会和亲戚进行一场这样的对话。你那个工作是不是做一些电脑零件啊什么的?不是,我做的是软件,是运行在电脑上的。那还是电脑上的东西吧?嗯...额,算是吧,是存到电脑上,运行在电脑上的。那不还是电脑的一部分,还是电脑零件啊。嗯...额,是,是电脑零件。

好了,言归正传,我们一起来看GC日志吧。
首先,是一段正常运行的GC日志:

# 注意GC日志的格式
2017-11-29T06:20:08.854+0800: 136737.156: [GC2017-11-29T06:20:08.854+0800: 136737.156:[ParNew: 3892864K->432512K(3892864K), 4.4839200 secs] 18183136K->16699325K(21194368K), 4.4841330 secs] [Times: user=81.77 sys=3.94, real=4.48 secs]
# NO.1 GC event开始的时间点   : GC时间的开始时间,相对于JVM的启动时间,单位是秒
# NO.2 新生代使用 ParNew 并行回收器,GC回收后被占用空间从3892864K降到432512K,新生代总容量为3892864K,耗时4秒
# NO.3 垃圾收集前整个heap区内存空间使用情况->垃圾收集后整个heap区内存空间使用情况, 回收时间
# NO.4 GC事件的持续时间Times
   user=垃圾收集线程消耗的所有CPU时间 
   sys=操作系统调用(OS call) 以及等待系统事件的时间
   real=应用程序暂停的时间(Clock time), 所以 real time 等于 user 以及 sys的总和

GC日志一般都是这样的格式,精简至极,但该有的内容都有。

再看一段异常日志:

# 注意,下面出现异常(concurrent mode failure)    
2017-11-29T06:20:15.538+0800: 136743.840: [GC2017-11-29T06:20:15.538+0800: 136743.841: [ParNew: 3892864K->3892864K(3892864K)3.7G, 0.0000330 secs]2017-11-29T06:20:15.538+0800: 136743.841: [CMS2017-11-29T06:20:34.452+0800: 136762.754: [CMS-concurrent-sweep: 19.039/19.042 secs] [Times: user=15.39 sys=4.18, real=19.05 secs](concurrent mode failure): 16210334K->10864227K(17301504K), 100.7676160 secs] 20103198K->10864227K(21194368K), [CMS Perm : 80725K->80722K(262144K)], 100.7678560 secs][Times: user=68.79 sys=31.64, real=100.77 secs]
# 让我们来分析下这段日志
# [ParNew: young区回收情况, 回收前3892864K, 回收后依然是3892864K, 显然, Young区已无内存可以分配,此时便会发生Full GC。
# 还有一种情况也会引发 Full GC : CMS采用多线程和应用线程并发执行, 以减少应用停顿时间, 但是, 当 old区内存不足, 没办法回收无引用的对象时, 自然也会发生Full GC
无论哪种情况, 都需要合理设置各代大小。若像当前这种由于young区空间不足以满足
# [CMS-concurrent-sweep: (concurrent mode failure): 

一般情况下,单次YGCT<0.020 s,FGCT <0.040 s

看CMS回收器怎么进行老年代回收:

# 开始使用CMS回收器进行老年代回收
2017-11-29T06:21:56.349+0800: 136844.651: [GC [1 CMS-initial-mark: 10864227K(17301504K)] 10938969K(21194368K), 0.0303920 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
# 初始标记阶段, 标记由根可以直接到达的对象,标记期间整个应用线程会暂停。老年代总量为17301504K,被占用10864227K;整个heap总量为21194368K,被占用10938969K
2017-11-29T06:21:56.379+0800: 136844.682: [CMS-concurrent-mark-start] 
 # 并发标记,在第一个阶段被暂停的线程重新开始运行,由前一阶段标记过的对象出发,所有可到达的对象都在本阶段中标记
2017-11-29T06:22:03.147+0800: 136851.449: [CMS-concurrent-mark: 6.734/6.768 secs] [Times: user=52.26 sys=5.69, real=6.77 secs] 
# 并发标记阶段结束, 占用 6.734秒CPU时间, 6.768秒墙钟时间(也包含线程让出CPU给其他线程执行的时间)
2017-11-29T06:22:03.147+0800: 136851.449: [CMS-concurrent-preclean-start]
# 并发预清理阶段, 预清理也是一个并发执行的阶段。在本阶段, 会查找前一阶段执行过程中, 从新生代晋升或新分配或被更新的对象。通过并发地重新扫描这些对象, 预清理阶段可以减少下一个stop-the-world 重新标记阶段的工作量。
2017-11-29T06:22:03.383+0800: 136851.685: [CMS-concurrent-preclean: 0.227/0.235 secs] [Times: user=0.56 sys=0.14, real=0.24 secs]
# 预清理阶段费时 0.017秒CPU时间,0.018秒墙钟时间。

2017-11-29T06:22:03.383+0800: 136851.685: [CMS-concurrent-abortable-preclean-start]
# 并发可中止预清理阶段。 可中止预清理阶段,运行在并行预清理和重新标记之间,直到获得所期望的eden空间占用率。增加这个阶段是为了避免在重新标记阶段后紧跟着发生一次垃圾清除。为了尽可能区分开垃圾清除和重新标记 ,我们尽量安排在两次垃圾清除之间运行重新标记阶段。
2017-11-29T06:22:04.099+0800: 136852.401: [GC2017-11-29T06:22:04.099+0800: 136852.401: [ParNew: 3460352K->432512K(3892864K), 1.1155530 secs] 14324579K->11784267K(21194368K), 1.1158800 secs] [Times: user=22.26 sys=0.85, real=1.13 secs]
# 竟然发生了一次ParNew GC

CMS: abort preclean due to time 2017-11-29T06:22:05.226+0800: 136853.528: [CMS-concurrent-abortable-preclean: 0.215/1.843 secs] [Times: user=23.59 sys=1.14, real=1.84secs]
# 并发可中止预清理阶段费时 0.215秒CPU时间,1.843秒墙钟时间。

2017-11-29T06:22:05.230+0800: 136853.532: [GC[YG occupancy: 456818 K (3892864 K)]2017-11-29T06:22:05.230+0800: 136853.532: [Rescan (parallel), 0.2224280 secs]2017-11-29T06:22:05.452+0800: 136853.755: [weak refs processing, 0.0002010 secs]2017-11-29T06:22:05.452+0800: 136853.755: [class unloading, 0.0199850 secs]2017-11-29T06:22:05.472+0800: 136853.775: [scrub symbol table, 0.0098660 secs]2017-11-29T06:22:05.482+0800: 136853.785: [scrub string table, 0.0016960 secs] [1 CMS-remark: 11351755K(17301504K)] 11808574K(21194368K), 0.2586050 secs] [Times: user=4.98 sys=0.06, real=0.26 secs]
# 重新标记, Stop-the-world 阶段,从根及被其引用对象开始,重新扫描 CMS 堆中残留的更新过的对象。这里重新扫描费时0.1790103秒,处理弱引用对象费时0.0100966秒,本阶段费时0.1897792 秒。

# Rescan (parallel)表示的是多线程处理young区和多线程扫描old+perm的卡表的总时间
# weak refs processing 处理old区的弱引用的总时间, 用于回收native memory
# class unloading 回收SystemDictionary消耗的总时间
2017-11-29T06:22:05.489+0800: 136853.791: [CMS-concurrent-sweep-start]
# 并发清理开始, 在清理阶段,应用线程还在运行。
2017-11-29T06:22:17.665+0800: 136865.967: [CMS-concurrent-sweep: 12.113/12.176 secs] [Times: user=18.13 sys=5.14, real=12.18 secs]
# 并发清理阶段耗时12.18秒
2017-11-29T06:22:17.665+0800: 136865.967: [CMS-concurrent-reset-start]
# 并发重置开始
2017-11-29T06:22:17.772+0800: 136866.074: [CMS-concurrent-reset: 0.107/0.107 secs] [Times: user=0.05 sys=0.06, real=0.10 secs]
# 并发重置阶段耗时
2017-11-29T06:22:19.775+0800: 136868.078: [GC [1 CMS-initial-mark: 11335708K(17301504K)]14506257K(21194368K), 5.3840860 secs] [Times: user=4.87 sys=0.50, real=5.38 secs]
# 在本阶段,重新初始化CMS内部数据结构,以备下一轮 GC 使用

2017-11-29T06:22:25.160+0800: 136873.462: [CMS-concurrent-mark-start]
2017-11-29T06:22:32.594+0800: 136880.896: [CMS-concurrent-mark: 7.434/7.434 secs] [Times: user=40.03 sys=4.51, real=7.44 secs]
2017-11-29T06:22:32.594+0800: 136880.896: [CMS-concurrent-preclean-start]
2017-11-29T06:22:32.681+0800: 136880.984: [CMS-concurrent-preclean: 0.087/0.087 secs] [Times: user=0.08 sys=0.01, real=0.08 secs]
2017-11-29T06:22:32.681+0800: 136880.984: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2017-11-29T06:22:38.443+0800: 136886.745: [CMS-concurrent-abortable-preclean: 5.761/5.761 secs] [Times: user=5.50 sys=0.29, real=5.77secs]

CMS大讲堂:

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。

CMS过程

1. 初始标记:在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的”根对象”开始,只扫描到能够和”根对象”直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。

  1. 并发标记:这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。

3. 并发预清理:并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段”重新标记”的工作,因为下一个阶段会Stop The World。

4. 重新标记:这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从”跟对象”开始向下追溯,并处理对象关联。

5. 并发清理:清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。

6. 并发重置:这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

CMS执行过程:

CMS缺点

CMS回收器采用的基础算法是Mark-Sweep。所有CMS不会整理、压缩堆空间。这样就会有一个问题:经过CMS收集的堆会产生空间碎片。

下面博客中 Jon 详细讨论了如何处理碎片问题:https://blogs.oracle.com/jonthecollector/entry/when_the_sum_of_the

需要更多的CPU资源。从上面的图可以看到,为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU,单纯靠线程切换是不靠谱的。

什么时候用CMS

如果你的应用程序对停顿比较敏感,并且在应用程序运行的时候可以提供更大的内存和更多的CPU(也就是硬件牛逼),那么使用CMS来收集会给你带来好处。还有,如果在JVM中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,527评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,687评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,640评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,957评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,682评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,011评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,009评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,183评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,714评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,435评论 3 359
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,665评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,148评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,838评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,251评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,588评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,379评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,627评论 2 380

推荐阅读更多精彩内容

  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 6,012评论 2 31
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,688评论 0 7
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,669评论 3 83
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,764评论 17 311
  • 那时候我们二十郎当岁,出行都不敢想像做高铁,更不奢望飞机。因为qiong。 印象最深的一次是从上海到郑州的旅程。那...
    张好强阅读 181评论 0 0