《Java Performance The Definitive Guide》GC部分讀書筆記

前言

最近斷斷續(xù)續(xù)看了《Java Performance The Definitive Guide》前六章冷溃,記錄一下關(guān)于GC部分的讀書筆記。

GC

本書的第五章和第六章主要講了關(guān)于GC相關(guān)的只是盖淡,由于目前線上機器多采用了CMS凿歼,所以重點關(guān)注CMS部分禁舷。先設(shè)置PrintGCDetails后牵咙,觀察gc.log

Understanding the Throughput Collector

這種類型的GC會對Young和Old都進行STW型的收集

YGC

YGC的日志大概長這樣

17.806: [GC [PSYoungGen: 227983K->14463K(264128K)]
280122K->66610K(613696K), 0.0169320 secs]
[Times: user=0.05 sys=0.00, real=0.02 secs]

該段日志表明本次gc發(fā)生在據(jù)程序開始運行12.806秒后(gclog的時間格式可以設(shè)置)攀唯。gc后,young區(qū)的對象占用了14MB內(nèi)存空間(在survivor區(qū))另凌。在gc前戒幔,young區(qū)對象占用了227MB。目前young區(qū)的總大小為264MB诗茎。
同時,整個heap的占用(包括young區(qū)和old區(qū))從280MB減少到227MB敢订,目前整個heap的大小為613MB。整個gc操作共耗時0.02s昭齐。整個gc程序消耗的cpu時間比realtime(實際時間)要長矾柜,主要是因為ygc是由多個線程(本次配置為4個線程)共同完成的就谜。

full gc

old區(qū)gc同時會清除young區(qū)的所有對象(包括survivor的對象)吁伺。Old區(qū)中僅剩的對象都有活躍引用租谈,所有這些對象都會被壓縮排列捆愁。

FUllGC的日志大概長這樣

64.546: [Full GC [PSYoungGen: 15808K->0K(339456K)]
[ParOldGen: 457753K->392528K(554432K)] 473561K->392528K(893888K)
[PSPermGen: 56728K->56728K(115392K)], 1.3367080 secs]
[Times: user=4.44 sys=0.01, real=1.34 secs]

這段日志表明,現(xiàn)在Young區(qū)總共占用0bytes(總的大小為339MB)呻逆。old區(qū)中的數(shù)據(jù)從457MB減少到了392MB咖城,因此整個heap使用從473MB減少到了392MB。permgen的大小沒有改變宜雀,在大多數(shù)full gc時握础,permgen大小不會該表(如果permgen區(qū)占滿了,JVM會觸發(fā)一次fullgc進行permgc禀综,此時permgen的占用大小才會改變)。因為full gc有更多工作需要做孤澎,所以花了1.3秒實際時間欠窒,4.4s的cpu時間(因為有四個并行線程)

總結(jié)

  • throughput collector有兩種操作:minor gc和full gc。
  • gc Log中的時間可以用來衡量gc對于應(yīng)用的影響

Understanding the CMS Collector

由于目前線上的機器大部分使用的是cms gc策略岖妄,所以重點關(guān)注了這部分

cms有三種基本的操作:

  • cms進行ygc(將停止所有應(yīng)用線程)
  • cms開啟concurrent cycle清除old區(qū)的數(shù)據(jù)
  • 如果有必要,cms進行full gc

young gc

cms ygc和throughput的ygc非常相似:data從eden移入到一個survivor區(qū)中衣吠,然后進去old區(qū)茶敏。
cms的gc log和throughput的非常相似

89.853: [GC 89.853: [ParNew: 629120K->69888K(629120K), 0.1218970 secs]
1303940K->772142K(2027264K), 0.1220090 secs]
[Times: user=0.42 sys=0.02, real=0.12 secs]

日志表明:young區(qū)當(dāng)下的大小為629MB,在gc后缚俏,占用了69MB(在survivor區(qū))惊搏。類似的贮乳,整個Heap的大小為2027MB,在gc后恬惯,仍然有772MB被占用向拆。整個過程花費了0.12s,耗費了0.42s的cpu時間酪耳。

concurrent cycle

根據(jù)heap的占用浓恳,jvm將開啟concurrent cycle。當(dāng)足夠滿的時候碗暗,jvm開啟backgroup線程從heap中移除對象颈将。需要注意的是Old區(qū)不會被壓縮整理:已分配的區(qū)域和空閑區(qū)域可能間隔。當(dāng)ygc后將對象從eden區(qū)移入到old區(qū)中言疗,jvm將使用上述空閑區(qū)域容納這些對象晴圾。

從gclog中可以看出,整個concurrent cycle 分成很多階段噪奄。即使concurrent cycle的主要過程使用background線程,但是仍然有些階段會導(dǎo)致短暫暫停所有應(yīng)用線程都毒。

initial mark phase

concurrent cycle開始于一個initial mark階段,這個階段暫停了所有的應(yīng)用手负。日志如下:

89.976: [GC [1 CMS-initial-mark: 702254K(1398144K)]
772530K(2027264K), 0.0830120 secs]
[Times: user=0.08 sys=0.00, real=0.08 secs]

這個階段主要是負責(zé)找出heap中所有的gc roots蝠猬。第一組數(shù)字表示說目前old區(qū)的大小為1398MB榆芦,對象占用了702MB。第二組數(shù)字說明整個heap的大小為2027MB崎淳,對象占用了772MB拣凹。在在initial mark階段嚣镜,應(yīng)用線程暫停了0.08s菊匿。

mark phase

接下來的就是mark 階段徽职,在這個階段中不會暫停應(yīng)用線程力麸。gc日志中會出現(xiàn)以下行:

90.059: [CMS-concurrent-mark-start]
90.887: [CMS-concurrent-mark: 0.823/0.828 secs]
[Times: user=1.11 sys=0.00, real=0.83 secs]

mark階段耗時0.83s(1.11秒的CPU時間)克蚂。由于只是進行mark摸恍,所以沒有出現(xiàn)和heap內(nèi)存占用相關(guān)的信息立镶。

preclean phase

該階段不會暫停應(yīng)用線程

abortable-preclean phase

有了preclean phase為啥還需要abortable-preclean phase呢,abortable-preclean phase到底是做什么用的缭召。
需要abortable-preclean階段主要考量是因為remark phase階段會需要STW(YGC也是需要STW的)。為了不讓系統(tǒng)在ygc之后馬上進行remark(因為這樣會連續(xù)經(jīng)歷兩次STW搪哪,時間拉長)
噩死。所以abortable-preclean會在young區(qū)差不多50%滿的時候開始行嗤。在ygc完成后垛耳,cms會根據(jù)之前的ygc時間計算下次ygc發(fā)生的時間栈雳,在預(yù)測young區(qū)差不多50%滿的時候停止abortable-preclean,然后緊接著remark階段開始蛀骇。

同時擅憔,由于remark階段會需要重新掃描young區(qū),所以會存在一個矛盾个榕。為了讓remark更快,young區(qū)必須更锌寥谩(最好是剛minor gc完)所以從remark時間最短的角度來看最好是每次remark之前都要觸發(fā)一次minor gc(設(shè)置CMSScavengeBeforeRemark)。但是這樣又會造成連續(xù)兩次STW(minor gc & remark)仿畸,造成系統(tǒng)不可用時間拉長簿晓。所以最好的解決方案就是為讓ygc和remark之間通過abortable-preclean階段隔開,并且會希望在abortable-preclean階段過程中發(fā)生一次minor gc谒臼,所以在young區(qū)50%滿的時候開始,在進行完一次monir gc底哥,并且估算差不多又一次young區(qū)50%滿的時候停止abortable-preclean(停止abortable-preclean不止取決于很多因素,通過CMSMaxAbortablePrecleanTime可以影響附较,超過CMSMaxAbortablePrecleanTime不管有沒有發(fā)生minor gc都會進入到remark階段事示,所以CMSMaxAbortablePrecleanTime應(yīng)該和minor gc周期保持關(guān)系)

remark phase

緊接著就是remark phase階段肖爵,日志如下:

90.892: [CMS-concurrent-abortable-preclean-start]
92.392: [GC 92.393: [ParNew: 629120K->69888K(629120K), 0.1289040 secs]

1331374K->803967K(2027264K), 0.1290200 secs]
[Times: user=0.44 sys=0.01, real=0.12 secs]
94.473: [CMS-concurrent-abortable-preclean: 3.451/3.581 secs]
[Times: user=5.03 sys=0.03, real=3.58 secs]
94.474: [GC[YG occupancy: 466937 K (629120 K)]
94.474: [Rescan (parallel) , 0.1850000 secs]
94.659: [weak refs processing, 0.0000370 secs]
94.659: [scrub string table, 0.0011530 secs]
[1 CMS-remark: 734079K(1398144K)]
1201017K(2027264K), 0.1863430 secs]
[Times: user=0.60 sys=0.01, real=0.18 secs]

上述日志包括了abortable-preclean和remark秒啦,通過abortable-preclean余境,使得minor gc和remark兩個stw階段之間差不多隔了半個minor gc周期含末。需要注意的是remark階段會scan整個heap,注意日志中的94.474: [GC[YG occupancy: 466937 K (629120 K)]
94.474: [Rescan (parallel) , 0.1850000 secs]

sweep phase

接下來的一個階段是concurrent sweep phase,這個階段不會stw

94.661: [CMS-concurrent-sweep-start]
95.223: [GC 95.223: [ParNew: 629120K->69888K(629120K), 0.1322530 secs]
999428K->472094K(2027264K), 0.1323690 secs]
[Times: user=0.43 sys=0.00, real=0.13 secs]
95.474: [CMS-concurrent-sweep: 0.680/0.813 secs]
[Times: user=1.45 sys=0.00, real=0.82 secs]

這個階段花費了大概0.82s。階段中間還經(jīng)歷了一次minor gc。需要說明的是minor gc可能會同時出現(xiàn)在old collection phases。在整個the sweep phase階段可能會出現(xiàn)任意多個minor gc。

reset phase

最后舞骆,是reset階段

95.474: [CMS-concurrent-reset-start]
95.479: [CMS-concurrent-reset: 0.005/0.005 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]

至此狈惫,整個CMS cycle完成了荸频。

CMS問題

concurrent mode failure

gc log會出現(xiàn)一些問題日志,表明cms遭遇了問題燎窘,首先就是‘concurrent mode failure’.

日志如下:

267.006: [GC 267.006: [ParNew: 629120K->629120K(629120K), 0.0000200 secs]
267.006: [CMS267.350: [CMS-concurrent-mark: 2.683/2.804 secs]
[Times: user=4.81 sys=0.02, real=2.80 secs]
(concurrent mode failure):
1378132K->1366755K(1398144K), 5.6213320 secs]
2007252K->1366755K(2027264K),
[CMS Perm : 57231K->57222K(95548K)], 5.6215150 secs]
[Times: user=5.63 sys=0.00, real=5.62 secs]

當(dāng)minor gc發(fā)生了,但是old區(qū)中沒有足夠的空間容納需要晉升的對象時,cms會觸發(fā)一次full gc。此時STW偷溺,所有應(yīng)用線程都會停止尉共,old區(qū)中dead對象會被清理拿愧,上述日志中表明old區(qū)清理到了136MB唾戚,但是full gc STW了5.62s。這個操作是單線程進行的测蹲,所以花費了很長時間齿椅。

promotion failed

第二個問題就是當(dāng)old區(qū)有足夠的空間給待晉升的對象時遣蚀,但是由于空閑內(nèi)存過于碎片化導(dǎo)致的。

6043.903: [GC 6043.903:
[ParNew (promotion failed): 614254K->629120K(629120K), 0.1619839 secs]
6044.217: [CMS: 1342523K->1336533K(2027264K), 30.7884210 secs]
2004251K->1336533K(1398144K),
[CMS Perm : 57231K->57231K(95548K)], 28.1361340 secs]
[Times: user=28.13 sys=0.38, real=28.13 secs]

日志表明芒涡,cms開始了一次ygc,而且認為old區(qū)中有足夠的空間來容納待晉升的對象(否則,將會導(dǎo)致concurrent mode failure問題)冬三,然后由于old區(qū)內(nèi)存過于碎片化勾笆,導(dǎo)致cms無法晉升這些對象弛车。

所以啃勉,在minor gc的過程中,cms嘗試去清理和壓縮整理old區(qū)叮阅。好消息是heap最終會被重新壓縮整理浩姥,碎片化問題得以解決。但是會帶來一個28秒的停頓時間。這個時間比發(fā)生‘concurrent mode failure’時消耗的時間要長魁淳,因為此時整個heap進行了壓縮整理界逛,而concurrent mode failure只free掉old區(qū)的內(nèi)存别惦。

permgen fill up

當(dāng)perm區(qū)滿了之后仁讨,需要被回收洞豁,會觸發(fā)一次Full gc

279.803: [Full GC 279.803:
[CMS: 88569K->68870K(1398144K), 0.6714090 secs]
558070K->68870K(2027264K),
[CMS Perm : 81919K->77654K(81920K)],
0.6716570 secs]

CMS問題解決手段(Tuning to Solve Concurrent Mode Failures)

concurrent mode or promotion failures產(chǎn)生的原因其實是由于‘老年代cms cycle和minor gc速度的不匹配’。

The old generation initially fills up by placing the objects right next to each other. When
some amount of the old generation is filled (by default, 70%), the concurrent cycle begins
and the background CMS thread(s) start scanning the old generation for garbage. At
this point, a race is on: CMS must complete scanning the old generation and freeing
objects before the remainder (30%) of the old generation fills up. If the concurrent cycle
loses the race, CMS will experience a concurrent mode failure.

可以從以下多個方面來避免:

  • 加大old區(qū)荒给,要不就是加大old區(qū)的比重丈挟,要不就是跳大heap 空間。
  • 使得cms cycles更頻繁
  • 使用更多的background 線程

Running the background thread more often

要想讓CMS贏得‘比賽’需要使得concurrent cycle更早的執(zhí)行志电。
我們可以通過設(shè)置-XX:CMSInitiatingOccupancyFraction=N 和 -XX:+UseCMSInitiatingOccupancyOnly改變開始執(zhí)行的計算邏輯曙咽。默認的UseCMSInitiatingOccupancyOnly是false,CMS會用一個很復(fù)雜的算法決定什么時候來時concurrent cycle.
當(dāng)UseCMSInitiatingOccupancyOnly設(shè)置為true時挑辆,默認的XX:CMSInitiatingOccupancyFraction是70例朱,也就是說在old區(qū)70%被占滿的時候開始concurrent cycle。

Adjusting the CMS background threads

Each CMS background thread will consume 100% of a CPU on a machine.cms gc后臺線程的數(shù)量可以通過-XX:ConcGCThreads=N設(shè)置鱼蝉。默認的話洒嗤,
ConcGCThreads = (3 + ParallelGCThreads) / 4

總結(jié)
  1. Avoiding concurrent mode failures is the key to achieving the
    best possible performance with CMS.
  1. The simplest way to avoid those failures (when possible) is to
    increase the size of the heap.
  2. Otherwise, the next step is to start the concurrent background
    threads sooner by adjusting the CMSInitiatingOccupancyFrac
    tion.
  3. Tuning the number of background threads can also help.

監(jiān)控手段

  • 最直接的監(jiān)控手段就是通過詳細的gc.log,我們可以通過腳本抓取并分析gc.log魁亦,可以通過關(guān)鍵詞檢測有無‘concurrent mode failures’和‘promotion failed’烁竭,有無‘Fullgc’等,原則上有這些關(guān)鍵字存在需要立即觸發(fā)warning報警吉挣。
  • 分析日志可以得出ygc的頻率和時長派撕,ygc頻率過高,平均時間拉長到閾值應(yīng)該觸發(fā)報警
  • 可以通過GarbageCollectorMBean獲取collectCount和collectTime睬魂,需要注意的是Count是累加總次數(shù)终吼,collectTime是累加的STW的總時間

總結(jié)

java performance GC相關(guān)讀書筆記,看到新的東西會繼續(xù)補充

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氯哮,一起剝皮案震驚了整個濱河市际跪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喉钢,老刑警劉巖姆打,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肠虽,居然都是意外死亡幔戏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門税课,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闲延,“玉大人痊剖,你說我怎么就攤上這事±萘幔” “怎么了陆馁?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長合愈。 經(jīng)常有香客問我叮贩,道長,這世上最難降的妖魔是什么佛析? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任妇汗,我火速辦了婚禮,結(jié)果婚禮上说莫,老公的妹妹穿的比我還像新娘杨箭。我一直安慰自己,他們只是感情好储狭,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布互婿。 她就那樣靜靜地躺著,像睡著了一般辽狈。 火紅的嫁衣襯著肌膚如雪慈参。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天刮萌,我揣著相機與錄音驮配,去河邊找鬼。 笑死着茸,一個胖子當(dāng)著我的面吹牛壮锻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涮阔,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猜绣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了敬特?” 一聲冷哼從身側(cè)響起掰邢,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伟阔,沒想到半個月后辣之,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡皱炉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年怀估,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃承。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奏夫,死狀恐怖怕篷,靈堂內(nèi)的尸體忽然破棺而出历筝,到底是詐尸還是另有隱情酗昼,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布梳猪,位于F島的核電站麻削,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏春弥。R本人自食惡果不足惜呛哟,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匿沛。 院中可真熱鬧扫责,春花似錦、人聲如沸逃呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抡笼。三九已至苏揣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間推姻,已是汗流浹背平匈。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留藏古,地道東北人增炭。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像拧晕,于是被迫代替她去往敵國和親弟跑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容