JAVA gc垃圾回收機制
一、GC概要
JVM堆相關知識
為什麼先說JVM堆?
JVM的堆是Java物件的活動空間,程式中的類的物件從中分配空間,其儲存著正在執行著的應用程式用到的所有物件。這些物件的建立方式就是那些new一類的操作,當物件無用後,是GC來負責這個無用的物件(地球人都知道)。
JVM堆
(1) 新域:儲存所有新成生的物件
(2) 舊域:新域中的物件,經過了一定次數的GC迴圈後,被移入舊域
(3)永久域:儲存類和方法物件,從配置的角度看,這個域是獨立的,不包括在JVM堆內。預設為4M。
新域會被分為3個部分:1.第一個部分叫Eden。(伊甸園??可能是因為亞當和夏娃是人類最早的活動物件?)2.另兩個部分稱為輔助生存空間(幼兒園),我這裡一個稱為A空間(From sqace),一個稱為B空間(To Space)。
二、GC淺談
GC的工作目的很明確:在堆中,找到已經無用的物件,並把這些物件佔用的空間收回使其可以重新利用.大多數垃圾回收的 演算法思路都是一致的:把所有物件組成一個集合,或可以理解為樹狀結構,從樹根開始找,只要可以找到的都是活動物件,如果找不到,這個物件就是凋零的昨日黃 花,應該被回收了。
在sun 的文件說明中,對JVM堆的新域,是採用coping演算法,該演算法的提出是為了克服控制代碼的開銷和解決堆碎片的垃圾回收。它開始時把堆分成一個物件面和多個 空閒面,程式從物件面為物件分配空間,當物件滿了,基於 coping演算法的垃圾收集就從根集中掃描活動物件,並將每個活動物件複製到空閒面(使得活動物件所佔的記憶體之間沒有空閒洞),這樣空閒面變成了物件面, 原來的物件面變成了空閒面,程式會在新的物件面中分配記憶體。
對於新生成的物件,都放在Eden中;當Eden充滿時(小孩太多 了),GC將開始工作,首先停止應用程式的執行,開始收集垃圾,把所有可找到的物件都複製到A空間中,一旦當A空間充滿,GC就把在A空間中可找到的物件 都複製到B空間中(會覆蓋原有的儲存物件),當B空間滿的時間,GC就把在B空間中可找到的物件都複製到A空間中,AB在這個過程中互換角色,那位客官說 了:拷來拷去,煩不煩啊?什麼時候是頭?您別急,在活動物件經過一定次數的GC操作後,這些活動物件就會被放到舊域中。對於這些活動物件,新域的幼兒園生 活結束了。新域為什麼要這麼折騰?起初在這塊我也很迷糊,又查了些資料,原來是這樣:應用程式生成的絕大部分物件都是短命的,copying演算法最理想的
狀態是,所有移出Eden的物件都會被收集,因為這些都是短命鬼,經過一定次數的GC後應該被收集,那麼移入到舊域的物件都是長命的,這樣可以防止AB空 間的來回複製影響應用程式。實際上這種理想狀態是很難達到的,應用程式中不可避免地存在長命的物件,copying演算法的發明者要這些物件都儘量放在新域 中,以保證小範圍的複製,壓縮舊域的開銷可比新域中的複製大得多(舊域在下面說)。對於舊域,採用的是tracing演算法的一種,稱為標記-清除-壓縮收 集器,注意,這有一個壓縮,這是個開銷挺大的操作。垃圾回收主要是對Young
Generation塊和Old Generation塊記憶體進行回收,YG用來放新產生的物件,經過幾次回收還沒回收掉的物件往OG中移動,對YG進行垃圾回收又叫做MinorGC,對 OG垃圾回收又叫MajorGC,兩塊記憶體回收互不干涉。二、Gc 流程:
[older generation][survivor 1][survivor 2][eden]
*young generation=eden + survivor
1.當eden滿了,觸發young GC;
2.young GC做2件事:一,去掉一部分沒用的object;二,把老的還被引用的object發到survior裡面,等下幾次GC以後,survivor再放到old裡面。
3.當old滿了,觸發full GC。full GC很消耗記憶體,把old,young裡面大部分垃圾回收掉。這個時候使用者執行緒都會被block。
三、young generation比例越大,不一定最好。
將young的大小設定為大於總堆大小的一半時會造成效率低下。如果設定得過小,又會因為young generation收集程式不得不頻繁執行而造成瓶頸。
四、總結
從上面的推導可以得出很多結論,下面是前輩的經驗總結與自已的認識
1.JVM堆的大小決定了GC的執行時間。如果JVM堆的大小超過一定的限度,那麼GC的執行時間會很長。
2.物件生存的時間越長,GC需要的回收時間也越長,影響了回收速度。
3.大多數物件都是短命的,所以,如果能讓這些物件的生存期在GC的一次執行週期內,wonderful!
4.應用程式中,建立與釋放物件的速度決定了垃圾收集的頻率。
5.如果GC一次執行週期超過3-5秒,這會很影響應用程式的執行,如果可以,應該減少JVM堆的大小了。
6.前輩經驗之談:通常情況下,JVM堆的大小應為實體記憶體的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
~~~~~~~~~~~~~~~~~~~~~~~~~~這三塊為上面所說的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
這臺機器簡單說YG記憶體1G,OG記憶體2G,總記憶體4G
在這樣的配置下,GC執行情況:
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 這裡發生了一次YG GC,也就是MinorGC,耗時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
同時可以看到總共進行了398次Major GC 總耗時2052.993 所以每次Major GC時間為:2052.993/398=5.16秒
這是個很嚴重的問題,進行Major GC的時候程式會暫停,無法響應,居然會暫停5秒多,這誰都無法接受吧 :)
同樣Minor GC進行了7647次,總用時1221.782 平均時間為0.16秒,算是可以接受
再來看看修改配置後:
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 大小和使用狀態
concurrent mark-sweep generation:
capacity = 3758096384 (3584.0MB)
used = 1680041600 (1602.2125244140625MB)
free = 2078054784 (1981.7874755859375MB)
44.70459052494594% used
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~OG 大小和使用狀態
Perm Generation:
capacity = 268435456 (256.0MB)
used = 128012184 (122.0819320678711MB)
free = 140423272 (133.9180679321289MB)
47.688254714012146% used
在這個配置下,GC執行情況:
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 這裡發生了一次YG GC,也就是MinorGC,耗時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平均時間:22.734/68=0.334秒(上面是5秒多吧)
MinorGC平均時間:2058.691/20823=0.099秒(比上面略少)