這是我公司同事的GC學習筆記,寫得蠻詳細的,由淺入深,循序漸進,讓人一看就懂,特轉到這里。
一、GC特性以及各種GC的選擇
1、垃圾回收器的特性
2、對垃圾回收器的選擇
2.1 連續 VS. 并行
2.2 并發 VS. stop-the-world
2.3 壓縮 VS. 不壓縮 VS. 復制
二、GC性能指標
三、分代回收
四、J2SE 5.0的HotSpot JVM上的GC學習 - 分代、GC類型、快速分配
五、J2SE 5.0的HotSpot JVM上的GC學習 - SerialGC
六、J2SE 5.0的HotSpot JVM上的GC學習 - ParallelGC
七、J2SE 5.0的HotSpot JVM上的GC學習 - ParallelCompactingGC
八、J2SE 5.0的HotSpot JVM上的GC學習 - CMS GC
九、啟動參數學習示例
1. GC特性以及各種GC的選擇
1.1 垃圾回收器的特性
該回收的對象一定要回收,不該回收的對象一定不能回收
一定要有效,并且要快!盡可能少的暫停應用的運行
需要在時間,空間,回收頻率這三個要素中平衡
內存碎片的問題(一種解決內存碎片的方法,就是壓縮)
可擴展性和可伸縮性(內存的分配和回收,不應該成為跑在多核多線程應用上的瓶頸)
對垃圾回收器的選擇
1.2 連續 VS. 并行
連續垃圾回收器,即使在多核的應用中,在回收時,也只有一個核被利用。
但并行GC會使用多核,GC任務會被分離成多個子任務,然后這些子任務在各個CPU上并行執行。
并行GC的好處是讓GC的時間減少,但缺點是增加了復雜度,并且存在產生內存碎片的可能。
1.3 并發 VS. stop-the-world
當使用stop-the-world 方式的GC在執行時,整個應用會暫停住的。
而并發是指GC可以和應用一起執行,不用stop the world。
一般的說,并發GC可以做到大部分的運行時間,是可以和應用并發的,但還是有一些小任務,不得不短暫的stop the world。
stop the world 的GC相對簡單,因為heap被凍結,對象的活動也已經停止。但缺點是可能不太滿足對實時性要求很高的應用。
相應的,并發GC的stop the world時間非常短,并且需要做一些額外的事情,因為并發的時候,對象的引用狀態有可能發生改變的。
所以,并發GC需要花費更多的時間,并且需要較大的heap。
1.4 壓縮 VS. 不壓縮 VS. 復制
在GC確定內存中哪些是有用的對象,哪些是可回收的對象之后,他就可以壓縮內存,把擁有的對象放到一起,并把剩下的內存進行清理。
在壓縮之后,分配對象就會快很多,并且內存指針可以很快的指向下一個要分配的內存地址。
一個不壓縮的GC,就原地把不被引用的對象回收,他并沒有對內存進行壓縮。好處就是回收的速度變快了;缺點呢,就是產生了碎片。
一般來說,在有碎片的內存上分配一個對象的代價要遠遠大于在沒有碎片的內存上分配。
另外的選擇是使用一個復制算法的GC,他是把所有被引用的對象復制到另外一個內存區域中。
使用復制GC的好處就是,原來的內存區域,就可以被毫無顧忌的清空了。但缺點也很明顯,需要更多的內存,以及額外的時間來復制。
2. GC性能指標
幾個評估GC性能的指標
吞吐量 應用花在非GC上的時間百分比
GC負荷 與吞吐量相反,指應用花在GC上的時間百分比
暫停時間 應用花在GC stop-the-world 的時間
GC頻率 顧名思義
FootPRint 一些資源大小的測量,比如堆的大小
反應速度 從一個對象變成垃圾道這個對象被回收的時間
一個交互式的應用要求暫停時間越少越好,然而,一個非交互性的應用,當然是希望GC負荷越低越好。
一個實時系統對暫停時間和GC負荷的要求,都是越低越好。
一個嵌入式系統當然希望Footprint越小越好。
3. 分代回收
3.1 什么是分代
當使用分代回收技術,內存會被分為幾個代(generation)。也就是說,按照對象存活的年齡,把對象放到不同的代中。
使用最廣泛的代,應屬年輕代和年老代了。
根據各種GC算法的特征,可以相應的被應用到不同的代中。
研究發現:
大部分的對象在分配后不久,就不被引用了。也就是,他們在很早就掛了。
只有很少的對象熬過來了。
年輕代的GC相當的頻繁,高效率并且快。因為年輕代通常比較小,并且很多對象都是不被引用的。
如果年輕代的對象熬過來了,那么就晉級到年老代中了。如圖:
通常年老代要比年輕代大,而且增長也比較慢。所以GC在年老代發生的頻率非常低,不過一旦發生,就會占據較長的時間。
3.2 總結
年輕代通常使用時間占優的GC,因為年輕代的GC非常頻繁
年老代通常使用善于處理大空間的GC,因為年老代的空間大,GC頻率低
4. J2SE 5.0的HotSpot JVM上的GC學習 - 分代、GC類型、快速分配
J2SE5.0 update 6 的HotSpot上有4個GC。
4.1 HotSpot上的分代
分成三部分:年輕代、年老代、永久代
很多的對象一開始是分配在年輕代的,這些對象在熬過了一定次數的young gc之后,就進入了年老代。同時,一些比較大的對象,一開始就可能被直接分配到年老代中(因為年輕代比較小嘛)。
4.2 年輕代
年輕代也進行劃分,劃分成:一個Eden和兩個survivor。如下圖:
大部分的對象被直接分配到年輕代的eden區(之前已經提到了是,很大的對象會被直接分配到年老代中),
survivor區里面放至少熬過一個YGC的對象,在survivor里面的對象,才有機會被考慮提升到年老代中。
同一時刻,兩個survivor只被使用一個,另外一個是用來進行復制GC時使用的。
4.3 GC類型
年輕代的GC叫young GC,有時候也叫 minor GC。年老代或者永久代的GC,叫 full GC,也叫major GC。
也就是說,所有的代都會進行GC。
一般的,首先是進行年輕代的GC,(使用針對年輕代的GC),然后是年老代和永久代使用相同的GC。如果要壓縮(解決內存碎片問題),每個代需要分別壓縮。
有時候,如果年老區本身就已經很滿了,滿到無法放下從survivor熬出來的對象,那么,YGC就不會再次觸發,而是會使用FullGC對整個堆進行GC(除了CMS這種GC,因為CMS不能對年輕代進行GC)
4.4 快速分配內存
多線程進行對象建立的時候,在為對象分配內存的時候,就應該保證線程安全,為此,就應該進入全局鎖。但全局鎖是非常消耗性能的。
為此,HotSpot引入了Thread Local Allocation Buffers (TLAB)技術,這種技術的原理就是為每個線程分配一個緩沖,用來分配線程自己的對象。
每個線程只使用自己的TLAB,這樣,就保證了不用使用全局鎖。當TLAB不夠用的時候,才需要使用全局鎖。但這時候對鎖的時候,頻率已經相當的低了。
為了減少TLAB對空間的消耗,分配器也想了很多方法,平均來說,TLAB占用Eden區的不到1%。
5. J2SE 5.0的HotSpot JVM上的GC學習 - SerialGC
5.1 串行GC
串行GC,只使用單個CPU,并且會stop the world。
5.1.1 young 的串行GC
如下圖:
當發生ygc的時候,Eden和From的survivor區會將被引用的對象復制到To這個survivor種。
如果有些對象在To survivor放不下,則直接升級到年老區。
當YGC完成后,內存情況如下圖:
5.1.2 old區的串行GC
年老區和永久區使用的是Mark-Sweep-Compact的算法。
mark階段是對有引用的對象進行標識
sweep是對垃圾進行清理
compact對把活著的對象進行遷移,解決內存碎片的問題
如下圖:
5.2 何時使用串行收集器
串行GC適用于對暫停時間要求不嚴,在客戶端下使用。
5.3 串行收集器的選擇
在J2SE5.0上,在非 server 模式下,JVM自動選擇串行收集器。
也可以顯示進行選擇,在java啟動參數中增加: -XX:+UseSerialGC 。
6. J2SE 5.0的HotSpot JVM上的GC學習 - ParallelGC
6.1 并行GC
現在已經有很多java應用跑在多核的機器上了。
并行的GC,也稱作吞吐量GC,這種GC把多個CPU都用上了,不讓CPU再空轉。
6.2 YGC的并行GC
YGC的情況,還是使用stop-the-world + 復制算法的GC。
只不過是不再串行,而是充分利用多個CPU,減少GC負荷,增加吞吐量。
如下圖,串行YGC和并行YGC的比較:
6.3 年老區的并行GC
也是和串行GC一樣,在年老區和永久區使用Mark-Sweep-Compact,利用多核增加了吞吐量和減少GC負荷。
6.4 何時使用并行GC
對跑在多核的機器上,并且對暫停時間要求不嚴格的應用。因為頻率較低,但是暫停時間較長的Full GC還是會發生的。
6.5 選擇并行GC
在server模式下,并行GC會被自動選擇。
或者可以顯式選擇并行GC,加啟動JVM時加上參數: -XX:UseParallelGC
7. J2SE 5.0的HotSpot JVM上的GC學習 - ParallelCompactingGC
7.1 Parallel Compacting GC
parallelCompactingGC是在J2SE5.0 update6 引入的。
parallel compacting GC 與 parallel GC的不同地方,是在年老區的收集使用了一個新的算法。并且以后,parallel compacting GC 會取代 parallem GC的。
7.2 YGC的并行壓縮GC
與并行GC使用的算法一樣:stop-the-world 和 復制。
7.3 年老區的并行壓縮GC
他將把年老區和永久區從邏輯上劃分成等大的區域。
分為三個階段:
標記階段,使用多線程對存在引用的對象進行并行標記。
分析階段,GC對各個區域進行分析,GC認為,在經過上次GC后,越左邊的區域,有引用的對象密度要遠遠大于右邊的區域。所以就從左往右分析,當某個區域的密度達到一個值的時候,就認為這是一個臨界區域,所以這個臨界區域左邊的區域,將不會進行壓縮,而右邊的區域,則會進行壓縮。
壓縮階段,多各個需要壓縮的區域進行并行壓縮。
7.4 什么時候使用并行壓縮GC
同樣的,適合在多核的機器上;并且此GC在FGC的時候,暫停時間會更短。
可以使用參數 -XX:ParallelGCThreads=n 來指定并行的線程數。
7.5 開啟并行壓縮GC
使用參數 -XX:+UseParallelOldGC
8. J2SE 5.0的HotSpot JVM上的GC學習 - CMS GC
8.1 Concurrent mark sweep GC
很多應用對響應時間的要求要大于吞吐量。
YGC并不暫停多少時間,但FGC對時間的暫用還是很長的。特別是在年老區使用的空間較多時。
因此, HotSpot引入了一個叫做CMS的收集器,也叫低延時收集器。
8.2 CMS的YGC
與并行GC同樣的方式: stop-the-world 加上 copy。
8.3 CMS的FGC
CMS的FGC在大部分是和應用程序一起并發的!
CMS在FGC的時候,一開始需要做一個短暫的暫停,這個階段稱為最初標記:識別所有被引用的對象。
在并發標記時候,會和應用程序一起運行。
因為并發標記是和程序一起運行的,所以在并發標記結束的時候,不能保證所有被引用的對象都被標記,
為了解決這個問題,GC又進行了一次暫停,這個階段稱為:重標識(remark)。
在這個過程中,GC會重新對在并發標階段時候有修改的對象做標記。
因為remark的暫停要大于最初標記,所以在這時候,需要使用多線程來并行標記。
在上述動作完成之后,就可以保證所有被引用的對象都被標記了。
因此,并發清理階段就可以并發的收集垃圾了。
下圖是serial gc 和 CMS gc 的對比:
因為要增加很多額外的動作,比如對被引用的對象重新標記,增加了CMS的工作量,所以他的GC負荷也相應的增加。
CMS是唯一沒有進行壓縮的GC。如下圖:
沒有壓縮,對于GC的過程,是節約了時間。但因此產生了內存碎片,所以對于新對象在年老區的分配,就產生了速度上的影響,
當然,也就包括了對YGC時間的影響了。
CMS的另一個缺點,就是他需要的堆比較大,因為在并發標記的時候和并發清除的時候,應用程序很有可能在不斷產生新的對象,而垃圾又還沒有被刪除。
另外,在最初標記之后的并發標記時,原先被引用的對象,有可能變成垃圾。但在這一次的GC中,這是沒有被刪除的。這種垃圾叫做:漂流垃圾。
最后,由于沒有進行壓縮,由此而帶來了內存碎片。
為了解決這個問題,CMS對熱點object大小進行了統計,并且估算之后的需求,然后把空閑的內存進行拆分或者合并來滿足后續的需求。
與其他的GC不同,CMS并不在年老區滿了之后才開始GC,他需要提前進行GC,用以滿足在GC同時需要額外的內存。
如果在GC的同時,內存不能滿足要求了,則GC就變成了并行GC或者串行GC。
為了防止這種情況,會根據上一次GC的統計來確定啟動時間。
或者是當年老區超過初始容量的話,CMS GC就會啟動。
初始容量的設置可以在JVM啟動時增加參數: -XX:CMSInitiatingOccupancyFraction=n
n是一個百分比,默認值為68。
總之,CMS比并行GC花費了更少的暫停時間,但是犧牲了吞吐量,以及需要更大的堆區。
8.4 額外模式
為了防止在并發標記的時候,GC線程長期占用CPU,CMS可以把并發標記的時候停下來,把cpu讓給應用程序。
收集器會想并發標記分解成很小的時間串任務,在YGC之間來執行。
這個功能對于機器的CPU個數少,但又想降低暫停時間的應用來說,非常有用。
8.5 何時使用CMS
當CPU資源較空閑,并且需要很低的暫停時間時,可以選擇CMS。比如 web servers。
8.6 選擇CMS
選擇CMS GC: 增加參數 -XX:UseConcMarkSweepGC
開啟額外模式: 增加參數 -XX:+CMSIncreamentalMode
9. 結合線上啟動參數學習
線上的啟動參數
-Dprogram.name=run.sh -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastaccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -Djava.awt.headless=true -Djava.NET.preferipv4Stack=true -Dcom.sun.management.config.file=/home/admin/web-deploy/conf/jmx/jmx_monitor_management.properties -Djboss.server.home.dir=/home/admin/web-deploy/jboss_server -Djboss.server.home.url=file/:/home/admin/web-deploy/jboss_server -Dapplication.codeset=GBK -Ddatabase.codeset=ISO-8859-1 -Ddatabase.logging=false -Djava.endorsed.dirs=/usr/alibaba/jboss/lib/endorsed
其中:
-Xmx2g -Xms2g 表示堆為2G
-Xmn256m 表示新生代為 256m
-Xss256k 設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右
-XX:PermSize=128m 表示永久區為128m
-XX:+DisableExplicitGC 禁用顯示的gc,程序程序中使用System.gc()中進行垃圾回收,使用這個參數后系統自動將 System.gc() 調用轉換成一個空操作
-XX:+UseConcMarkSweepGC 表示使用CMS
-XX:+CMSParallelRemarkEnabled 表示并行remark
-XX:+UseCMSCompactAtFullCollection 表示在FGC之后進行壓縮,因為CMS默認不壓縮空間的。
-XX:+UseCMSInitiatingOccupancyOnly 表示只在到達閥值的時候,才進行CMS GC
-XX:CMSInitiatingOccupancyFraction=70 設置閥值為70%,默認為68%。
-XX:+UseCompressedOops JVM優化之壓縮普通對象指針(CompressedOops),通常64位JVM消耗的內存會比32位的大1.5倍,這是因為對象指針在64位架構下,長度會翻倍(更寬的尋址)。對于那些將要從32位平臺移植到64位的應用來說,平白無辜多了1/2的內存占用,這是開發者不愿意看到的。幸運的是,從JDK 1.6 update14開始,64 bit JVM正式支持了 -XX:+UseCompressedOops 這個可以壓縮指針,起到節約內存占用的新參數.
關于-XX:+UseCMSInitiatingOccupancyOnly 和 -XX:CMSInitiatingOccupancyFraction ,詳細解釋見下:
The concurrent collection generally cannot be sped up but it can be started earlier.
A concurrent collection starts running when the percentage of allocated space in the old generation crosses a threshold. This threshold is calculated based on general experience with the concurrent collector. If full collections are occurring, the concurrent collections may need to be started earlier. The command line flag CMSInitiatingOccupancyFraction can be used to set the level at which the collection is started. Its default value is approximately 68%. The command line to adjust the value is
-XX:CMSInitiatingOccupancyFraction=<percent>
The concurrent collector also keeps statistics on the promotion rate into the old generation for the application and makes a prediction on when to start a concurrent collection based on that promotion rate and the available free space in the old generation. Whereas the use of CMSInitiatingOccupancyFraction must be conservative to avoid full collections over the life of the application, the start of a concurrent collection based on the anticipated promotion adapts to the changing requirements of the application. The statistics that are used to calculate the promotion rate are based on the recent concurrent collections. The promotion rate is not calculated until at least one concurrent collection has completed so at least the first concurrent collection has to be initiated because the occupancy has reached CMSInitiatingOccupancyFraction . Setting CMSInitiatingOccupancyFraction to 100 would not cause only the anticipated promotion to be used to start a concurrent collection but would rather cause only non-concurrent collections to occur since a concurrent collection would not start until it was already too late. To eliminate the use of the anticipated promotions to start a concurrent collection set UseCMSInitiatingOccupancyOnly to true.
-XX:+UseCMSInitiatingOccupancyOnly
關于內存管理完整詳細信息,請查看這份文檔:http://www.Oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf
新聞熱點
疑難解答