1. 程式人生 > >jvm-gc二

jvm-gc二

index 對象大小 https heap else 方式 應用 lag 階段

由於heap中對象的存活時間差異很大,如果每一次都是無差別的進行gc,效率會很差。將heap按照對象大小、存活時間劃分出不同的區域,針對不同的區域使用不同的gc算法可以提高效率。

技術分享

年輕代的對象存活率低可以采用復制算法,老年代的對象或是存活率高的對象,或是大對象,這些對象使用復制算法進行gc成本太高,所以老年代的gc可以采用標記-清除(可以選擇是否進行壓縮)。

ps:雖然每個區域的垃圾收集算法較為固定,但每個垃圾收集器的工作邏輯又有差別(比如單線程、多線程,並發、並行等)。

Serial

技術分享

-XX:+UseSerialGC,顯示指定Serial新生代垃圾收集器(Serial Old是Serial的老年代版本)

ParNew

技術分享

-XX:+UseParNewGC,顯示指定ParNew新生代垃圾收集器,ParNew是Serial的多線程版本(通過-XX:+UseConcMarkSweepGC設置CMS為老年代垃圾收集器後,新生代默認就為ParNew);

CMS

技術分享

  • 初始標記:STW,單線程,由於是從GCRoot尋找直達的對象,速度快;

  • 並發標記:與應用線程一起運行,是CMS最主要的工作階段,通過直達對象,掃描全部的對象,進行標記;

  • 重新標記:STW,修正並發標記時由於應用程序還在並發運行產生的對象的修改(標記在可達路徑上新增的對象),多線程,速度快,需要全局停頓;

  • 並發清除:與應用程序一起運行。CMS主要關註低延遲,因而采用並發方式,清理垃圾時,應用程序還在運行。如果開啟壓縮算法,則涉及到要移動應用程序的存活對象,此時不停頓,是很難處理的,一般需要停頓下,移動存活對象,再讓應用程序繼續運行,但這樣停頓時間變長,延遲變大

-XX:+UseConcMarkSweepGC,使用CMS收集器;

-XX:+ UseCMSCompactAtFullCollection,Full GC後,進行一次碎片整理;整理過程是獨占的,會引起停頓時間變長;

-XX:+CMSFullGCsBeforeCompaction=n,設置進行n次Full GC後,進行一次碎片整理;

-XX:ParallelCMSThreads=n,在進行並行GC的時候,用於GC的線程數,比如ParNew在對新生代gc時會STW,這是會有n個線程並行gc;

-XX:ConcGCThreads=n,設定CMS並發的線程數量(如果指定了ConcGCThreads=n,則除初始標記外,其余階段都會使用n個線程,如果沒有指定ConcGCThreads,則ConcGCThreads = (ParallelGCThreads + 3)/4);

-XX:CMSInitiatingOccupancyFraction=n,老年代使用n%後觸發CMS垃圾回收;

技術分享
 1    /** ParallelGCThreads的取值策略:
 2     ①如果用戶顯示指定了ParallelGCThreads,則使用用戶指定的值。
 3     ②否則,需要根據實際的CPU所能夠支持的線程數來計算ParallelGCThreads的值,計算方法見步驟③和步驟④。
 4     ③如果物理CPU所能夠支持線程數小於8,則ParallelGCThreads的值為CPU所支持的線程數。這裏的閥值為8,是因為JVM中調用nof_parallel_worker_threads接口所傳入的switch_pt的值均為8。
 5     ④如果物理CPU所能夠支持線程數大於8,則ParallelGCThreads的值為8加上一個調整值,調整值的計算方式為:物理CPU所支持的線程數減去8所得值的5/8或者5/16,JVM會根據實際的情況來選擇具體是乘以5/8還是5/16。
 6     比如,在64線程的x86 CPU上,如果用戶未指定ParallelGCThreads的值,則默認的計算方式為:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。
 7     */
 8 unsigned int VM_Version::calc_parallel_worker_threads() {
 9   unsigned int result;
10   if (is_M_series()) {
11     // for now, use same gc thread calculation for M-series as for niagara-plus
12     // in future, we may want to tweak parameters for nof_parallel_worker_thread
13     result = nof_parallel_worker_threads(5, 16, 8);
14   } else if (is_niagara_plus()) {
15     result = nof_parallel_worker_threads(5, 16, 8);
16   } else {
17     result = nof_parallel_worker_threads(5, 8, 8);
18   }
19   return result;
20 } 
21 
22 unsigned int Abstract_VM_Version::parallel_worker_threads() {
23   if (!_parallel_worker_threads_initialized) {
24     if (FLAG_IS_DEFAULT(ParallelGCThreads)) {
25       _parallel_worker_threads = VM_Version::calc_parallel_worker_threads();
26     } else {
27       _parallel_worker_threads = ParallelGCThreads;
28     }
29     _parallel_worker_threads_initialized = true;
30   }
31   return _parallel_worker_threads;
32 }
33 
34 unsigned int Abstract_VM_Version::calc_parallel_worker_threads() {
35   return nof_parallel_worker_threads(5, 8, 8);
36 }
37 
38 unsigned int Abstract_VM_Version::nof_parallel_worker_threads(
39                                                       unsigned int num,
40                                                       unsigned int den,
41                                                       unsigned int switch_pt) {
42   if (FLAG_IS_DEFAULT(ParallelGCThreads)) {
43     assert(ParallelGCThreads == 0, "Default ParallelGCThreads is not 0");
44     // For very large machines, there are diminishing returns
45     // for large numbers of worker threads.  Instead of
46     // hogging the whole system, use a fraction of the workers for every
47     // processor after the first 8.  For example, on a 72 cpu machine
48     // and a chosen fraction of 5/8
49     // use 8 + (72 - 8) * (5/8) == 48 worker threads.
50     unsigned int ncpus = (unsigned int) os::active_processor_count();
51     return (ncpus <= switch_pt) ?
52            ncpus :
53           (switch_pt + ((ncpus - switch_pt) * num) / den);
54   } else {
55     return ParallelGCThreads;
56   }
57 } 
View Code

我們知道CMS初始標記只標記了GCRoot,並發標記是與應用線程並發進行的,如果某對象a剛被標記就變成了垃圾(沒有引用指向它),CMS是沒有任何辦法的,這就產生了“浮動垃圾”,“浮動垃圾”只能等到下一次gc進行回收。

由於並發標記、並發清除都是與應用程序線程並發進行的,這就會產生一種危險,即並發gc過程中應用程序產生了新對象,而此時gc還未完成,還未釋放出足夠的空間存放這個新對象。可以通過UseCMSInitiatingOccupancyOnly來指定當老年代使用了多少空間後觸發full gc,如果此後在gc過程中仍然出現了空間不足的情況,CMS就會出現“Concurrent Mode Failure”,這時jvm會啟動後備預案,即臨時啟動Serial Old收集器來重新進行老年代的垃圾收集。

不管是minor gc還是major gc,都會涉及到老年代和新生代的gc root。在進行新生代垃圾回收時,GC root可能在老年代中,而GC root可達的對象在新生代中,這種情況對於新生代垃圾回收來說,老年代對象指向新生代對象的引用才是事實上的GC root。而對於GC root在新生代,GC root可達的對象在老年代中,這種情況對於新生代垃圾回收來說,老年代中的可達對象會被忽略。

參考:http://www.cnblogs.com/ityouknow/p/5614961.html

  http://blog.csdn.net/bdx_hadoop_opt/article/details/38021209

  https://plumbr.eu/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc

  http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

jvm-gc二