GC學習筆記

GC學習筆記

這是我公司同事的GC學習筆記,寫得蠻詳細的,由淺入深,循序漸進,讓人一看就懂,特轉到這裏。

一、GC特性以及各種GC的選擇

1、垃圾回收器的特性

2、對垃圾回收器的選擇

2.1 連續 VS. 並行

2.2 併發 VS. stop-the-world

2.3 壓縮 VS. 不壓縮 VS. 複製

二、GC性能指標

三、分代回收

四、J2SE 5.0HotSpot JVM上的GC學習 分代、GC類型、快速分配

五、J2SE 5.0HotSpot JVM上的GC學習 - SerialGC

六、J2SE 5.0HotSpot JVM上的GC學習 - ParallelGC

七、J2SE 5.0HotSpot JVM上的GC學習 - ParallelCompactingGC

八、J2SE 5.0HotSpot 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被凍結,對象的活動也已經停止。但缺點是可能不太滿足對實時性要求很高的應用。

相應的,併發GCstop 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.0HotSpot JVM上的GC學習 分代、GC類型、快速分配 

J2SE5.0 update 6 HotSpot上有4GC

4.1 HotSpot上的分代

分成三部分:年輕代、年老代、永久代

很多的對象一開始是分配在年輕代的,這些對象在熬過了一定次數的young gc之後,就進入了年老代。同時,一些比較大的對象,一開始就可能被直接分配到年老代中(因爲年輕代比較小嘛)。

4.2 年輕代

年輕代也進行劃分,劃分成:一個Eden和兩個survivor。如下圖:

大部分的對象被直接分配到年輕代的eden區(之前已經提到了是,很大的對象會被直接分配到年老代中),

survivor區裏面放至少熬過一個YGC的對象,在survivor裏面的對象,纔有機會被考慮提升到年老代中。

同一時刻,兩個survivor只被使用一個,另外一個是用來進行復制GC時使用的。

4.3 GC類型

年輕代的GCyoung 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.0HotSpot JVM上的GC學習 - SerialGC 

5.1 串行GC

 串行GC,只使用單個CPU,並且會stop the world

5.1.1 young 的串行GC

如下圖: 

 

當發生ygc的時候,EdenFromsurvivor區會將被引用的對象複製到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.0HotSpot 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.0HotSpot 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

同樣的,適合在多核的機器上;並且此GCFGC的時候,暫停時間會更短。

可以使用參數 -XX:ParallelGCThreads=n 來指定並行的線程數。

7.5 開啓並行壓縮GC

使用參數 -XX:+UseParallelOldGC

8. J2SE 5.0HotSpot JVM上的GC學習 - CMS GC 

8.1 Concurrent mark sweep GC

很多應用對響應時間的要求要大於吞吐量。

YGC並不暫停多少時間,但FGC對時間的暫用還是很長的。特別是在年老區使用的空間較多時。

因此, HotSpot引入了一個叫做CMS的收集器,也叫低延時收集器。

8.2 CMSYGC

與並行GC同樣的方式: stop-the-world 加上 copy

8.3 CMSFGC

CMSFGC在大部分是和應用程序一起併發的!

CMSFGC的時候,一開始需要做一個短暫的暫停,這個階段稱爲最初標記:識別所有被引用的對象。

在併發標記時候,會和應用程序一起運行。

因爲併發標記是和程序一起運行的,所以在併發標記結束的時候,不能保證所有被引用的對象都被標記,

爲了解決這個問題,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線程長期佔用CPUCMS可以把併發標記的時候停下來,把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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章