GC學習筆記

GC學習筆記java

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

1、GC特性以及各類GC的選擇算法

一、垃圾回收器的特性安全

二、對垃圾回收器的選擇多線程

2.1 連續 VS. 並行架構

2.2 併發 VS. stop-the-world併發

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

2、GC性能指標app

3、分代回收less

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

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

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

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

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

9、啓動參數學習示例

 

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

 

轉自:http://blog.csdn.net/fenglibing/article/details/6321453

相關文章
相關標籤/搜索