一、GC概要:
JVM堆相關(guān)知識(shí)
為什么先說(shuō)JVM堆?
JVM的堆是Java對(duì)象的活動(dòng)空間,程序中的類的對(duì)象從中分配空間,其存儲(chǔ)著正在運(yùn)行著的應(yīng)用程序用到的所有對(duì)象。這些對(duì)象的建立方式就是那些new一類的操作,當(dāng)對(duì)象無(wú)用后,是GC來(lái)負(fù)責(zé)這個(gè)無(wú)用的對(duì)象(地球人都知道)。
JVM堆
(1) 新域:存儲(chǔ)所有新成生的對(duì)象
(2) 舊域:新域中的對(duì)象,經(jīng)過(guò)了一定次數(shù)的GC循環(huán)后,被移入舊域
(3)永久域:存儲(chǔ)類和方法對(duì)象,從配置的角度看,這個(gè)域是獨(dú)立的,不包括在JVM堆內(nèi)。默認(rèn)為4M。
新域會(huì)被分為3個(gè)部分:1.第一個(gè)部分叫Eden。(伊甸園??可能是因?yàn)閬啴?dāng)和夏娃是人類最早的活動(dòng)對(duì)象?)2.另兩個(gè)部分稱為輔助生存空間(幼兒園),我這里一個(gè)稱為A空間(From sqace),一個(gè)稱為B空間(To Space)。

GC淺談:
GC的工作目的很明確:在堆中,找到已經(jīng)無(wú)用的對(duì)象,并把這些對(duì)象占用的空間收回使其可以重新利用.大多數(shù)垃圾回收的算法思路都是一致的:把所有對(duì)象組成一個(gè)集合,或可以理解為樹狀結(jié)構(gòu),從樹根開始找,只要可以找到的都是活動(dòng)對(duì)象,如果找不到,這個(gè)對(duì)象就是凋零的昨日黃花,應(yīng)該被回收了。
在sun 的文檔說(shuō)明中,對(duì)JVM堆的新域,是采用coping算法,該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時(shí)把堆分成一個(gè)對(duì)象面和多個(gè)空閑面,程序從對(duì)象面為對(duì)象分配空間,當(dāng)對(duì)象滿了,基于 coping算法的垃圾收集就從根集中掃描活動(dòng)對(duì)象,并將每個(gè)活動(dòng)對(duì)象復(fù)制到空閑面(使得活動(dòng)對(duì)象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對(duì)象面,原來(lái)的對(duì)象面變成了空閑面,程序會(huì)在新的對(duì)象面中分配內(nèi)存。
對(duì)于新生成的對(duì)象,都放在Eden中;當(dāng)Eden充滿時(shí)(小孩太多了),GC將開始工作,首先停止應(yīng)用程序的運(yùn)行,開始收集垃圾,把所有可找到的對(duì)象都復(fù)制到A空間中,一旦當(dāng)A空間充滿,GC就把在A空間中可找到的對(duì)象都復(fù)制到B空間中(會(huì)覆蓋原有的存儲(chǔ)對(duì)象),當(dāng)B空間滿的時(shí)間,GC就把在B空間中可找到的對(duì)象都復(fù)制到A空間中,AB在這個(gè)過(guò)程中互換角色,那位客官說(shuō)了:拷來(lái)拷去,煩不煩啊?什么時(shí)候是頭?您別急,在活動(dòng)對(duì)象經(jīng)過(guò)一定次數(shù)的GC操作后,這些活動(dòng)對(duì)象就會(huì)被放到舊域中。對(duì)于這些活動(dòng)對(duì)象,新域的幼兒園生活結(jié)束了。
新域?yàn)槭裁匆@么折騰?
起初在這塊我也很迷糊,又查了些資料,原來(lái)是這樣:應(yīng)用程序生成的絕大部分對(duì)象都是短命的,copying算法最理想的狀態(tài)是,所有移出Eden的對(duì)象都會(huì)被收集,因?yàn)檫@些都是短命鬼,經(jīng)過(guò)一定次數(shù)的GC后應(yīng)該被收集,那么移入到舊域的對(duì)象都是長(zhǎng)命的,這樣可以防止AB空間的來(lái)回復(fù)制影響應(yīng)用程序。
實(shí)際上這種理想狀態(tài)是很難達(dá)到的,應(yīng)用程序中不可避免地存在長(zhǎng)命的對(duì)象,copying算法的發(fā)明者要這些對(duì)象都盡量放在新域中,以保證小范圍的復(fù)制,壓縮舊域的開銷可比新域中的復(fù)制大得多(舊域在下面說(shuō))。
對(duì)于舊域,采用的是tracing算法的一種,稱為標(biāo)記-清除-壓縮收集器,注意,這有一個(gè)壓縮,這是個(gè)開銷挺大的操作。
垃圾回收主要是對(duì)Young Generation塊和Old Generation塊內(nèi)存進(jìn)行回收,YG用來(lái)放新產(chǎn)生的對(duì)象,經(jīng)過(guò)幾次回收還沒回收掉的對(duì)象往OG中移動(dòng),對(duì)YG進(jìn)行垃圾回收又叫做MinorGC,對(duì)OG垃圾回收又叫MajorGC,兩塊內(nèi)存回收互不干涉。
二、Gc 流程:
[older generation][survivor 1][survivor 2][eden]
*young generation=eden + survivor
1.當(dāng)eden滿了,觸發(fā)young GC;
2.young GC做2件事:一,去掉一部分沒用的object;二,把老的還被引用的object發(fā)到survior里面,等下幾次GC以后,survivor再放到old里面。
3.當(dāng)old滿了,觸發(fā)full GC。full GC很消耗內(nèi)存,把old,young里面大部分垃圾回收掉。這個(gè)時(shí)候用戶線程都會(huì)被block。
三、young generation比例越大,不一定最好。
將young的大小設(shè)置為大于總堆大小的一半時(shí)會(huì)造成效率低下。如果設(shè)置得過(guò)小,又會(huì)因?yàn)閥oung generation收集程序不得不頻繁運(yùn)行而造成瓶頸。
四、總結(jié)
從上面的推導(dǎo)可以得出很多結(jié)論,下面是前輩的經(jīng)驗(yàn)總結(jié)與自已的認(rèn)識(shí)
1.JVM堆的大小決定了GC的運(yùn)行時(shí)間。如果JVM堆的大小超過(guò)一定的限度,那么GC的運(yùn)行時(shí)間會(huì)很長(zhǎng)。
2.對(duì)象生存的時(shí)間越長(zhǎng),GC需要的回收時(shí)間也越長(zhǎng),影響了回收速度。
3.大多數(shù)對(duì)象都是短命的,所以,如果能讓這些對(duì)象的生存期在GC的一次運(yùn)行周期內(nèi),wonderful!
4.應(yīng)用程序中,建立與釋放對(duì)象的速度決定了垃圾收集的頻率。
5.如果GC一次運(yùn)行周期超過(guò)3-5秒,這會(huì)很影響應(yīng)用程序的運(yùn)行,如果可以,應(yīng)該減少JVM堆的大小了。
6.前輩經(jīng)驗(yàn)之談:通常情況下,JVM堆的大小應(yīng)為物理內(nèi)存的80%。
五、看案例:
jmap -heap 2343
Attaching to process ID 2343, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0-b16
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 2686976 (2.5625MB)
MaxNewSize = -65536 (-0.0625MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2 (YG,OG 大小比為1:2)
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 268435456 (256.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 1260060672 (1201.6875MB)
used = 64868288 (61.86322021484375MB)
free = 1195192384 (1139.8242797851562MB)
5.148028935546367% used
From Space:
capacity = 85524480 (81.5625MB)
used = 59457648 (56.70323181152344MB)
free = 26066832 (24.859268188476562MB)
69.52120375359195% used
To Space:
capacity = 85852160 (81.875MB)
used = 0 (0.0MB)
free = 85852160 (81.875MB)
0.0% used
~~~~~~~~~~~~~~~~~~~~~~~~~~這三塊為上面所說(shuō)的YG大小和使用情況
PS Old Generation
capacity = 2291138560 (2185.0MB)
used = 1747845928 (1666.8757705688477MB)
free = 543292632 (518.1242294311523MB)
76.28722062099989% used
~~~~~~~~~~~~~~~~~~~~~~~~~~OG大小和使用情況
PS Perm Generation
capacity = 108265472 (103.25MB)
used = 107650712 (102.6637191772461MB)
free = 614760 (0.5862808227539062MB)
99.43217353728436% used
這臺(tái)機(jī)器簡(jiǎn)單說(shuō)YG內(nèi)存1G,OG內(nèi)存2G,總內(nèi)存4G
在這樣的配置下,GC運(yùn)行情況:
jstat -gcutil -h5 2343 4s 100
S0 S1 E O P YGC YGCT FGC FGCT GCT
79.82 0.00 75.34 78.55 99.44 7646 1221.668 398 2052.993 3274.661
0.00 79.52 0.62 78.63 99.44 7647 1221.782 398 2052.993 3274.775 這里發(fā)生了一次YG GC,也就是MinorGC,耗時(shí)0.12s
0.00 79.52 28.95 78.63 99.44 7647 1221.782 398 2052.993 3274.775
0.00 79.52 46.34 78.63 99.44 7647 1221.782 398 2052.993 3274.775
同時(shí)可以看到總共進(jìn)行了398次Major GC 總耗時(shí)2052.993 所以每次Major GC時(shí)間為:2052.993/398=5.16秒
這是個(gè)很嚴(yán)重的問(wèn)題,進(jìn)行Major GC的時(shí)候程序會(huì)暫停,無(wú)法響應(yīng),居然會(huì)暫停5秒多,這誰(shuí)都無(wú)法接受吧 :)
同樣Minor GC進(jìn)行了7647次,總用時(shí)1221.782 平均時(shí)間為0.16秒,算是可以接受
再來(lái)看看修改配置后:
jmap -heap 14103
Attaching to process ID 14103, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0-b16
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 536870912 (512.0MB)
MaxNewSize = 536870912 (512.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio =4 YG:OG 1:4
SurvivorRatio = 8
PermSize = 268435456 (256.0MB)
MaxPermSize = 268435456 (256.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 483196928 (460.8125MB)
used = 428284392 (408.4438247680664MB)
free = 54912536 (52.368675231933594MB)
88.63557841162434% used
Eden Space:
capacity = 429522944 (409.625MB)
used = 404788608 (386.0364990234375MB)
free = 24734336 (23.5885009765625MB)
94.24144010337199% used
From Space:
capacity = 53673984 (51.1875MB)
used = 23495784 (22.407325744628906MB)
free = 30178200 (28.780174255371094MB)
43.77499534970238% used
To Space:
capacity = 53673984 (51.1875MB)
used = 0 (0.0MB)
free = 53673984 (51.1875MB)
0.0% used
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~YG 大小和使用狀態(tài)
concurrent mark-sweep generation:
capacity = 3758096384 (3584.0MB)
used = 1680041600 (1602.2125244140625MB)
free = 2078054784 (1981.7874755859375MB)
44.70459052494594% used
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~OG 大小和使用狀態(tài)
Perm Generation:
capacity = 268435456 (256.0MB)
used = 128012184 (122.0819320678711MB)
free = 140423272 (133.9180679321289MB)
47.688254714012146% used
在這個(gè)配置下,GC運(yùn)行情況:
jstat -gcutil -h5 14103 4s 100
S0 S1 E O P YGC YGCT FGC FGCT GCT
47.49 0.00 64.82 46.08 47.69 20822 2058.631 68 22.734 2081.365
0.00 37.91 38.57 46.13 47.69 20823 2058.691 68 22.734 2081.425 這里發(fā)生了一次YG GC,也就是MinorGC,耗時(shí)0.06s
46.69 0.00 15.19 46.18 47.69 20824 2058.776 68 22.734 2081.510
46.69 0.00 74.59 46.18 47.69 20824 2058.776 68 22.734 2081.510
0.00 40.29 19.95 46.24 47.69 20825 2058.848 68 22.734 2081.582
MajorGC平均時(shí)間:22.734/68=0.334秒(上面是5秒多吧)
MinorGC平均時(shí)間:2058.691/20823=0.099秒(比上面略少)