|
誰需要GC調優(yōu) 小規(guī)模程序,垃圾收集算法能很好的工作。因為里面的對象圖不大,所以收集代價不高。但是如果是大規(guī)模的程序,對象成百上千,一次遍歷,就算是復制收集器中只對活動對象的遍歷,都需要很長的時間。所以,大規(guī)模程序,有必要深入了解GC的工作方式,了解和調整GC的參數。 關于串行和并行GC收集算法 在JVM 1.3.1以前,只有串行收集器,沒有并行收集器,對于多處理器,系統(tǒng)吞吐量損失很大。見下圖。為此,1.3.1以后(不包含1.3.1),引入了并發(fā)GC收集算法。 JVM1.4.2上,有4種垃圾收集器,默認會選擇串行收集器。 JVM5.0后,則會根據用戶機器的類型自動選擇收集器。 
1% GC 中的 1%是指,在一個CPU上執(zhí)行且只花1%的CPU時間做垃圾收集的應用程序。 可以看到,當這個程序在處理器為30個以上的系統(tǒng)上運行,系統(tǒng)的吞吐量會下降到80%以下,即本來在單CPU系統(tǒng)上只花1%時間做搜集的程序會在30個以上CPU的系統(tǒng)上,花上20%的時間做收集。 為什么會這樣呢?因為單CPU收集算法在多CPU系統(tǒng)上面做收集的時候,GC算法會暫停運行在任何CPU上面的程序,而自己卻只能利用一個CPU做GC,所以造成了這種情況。
代Generation
自從JVM1.2以后,Sun采用了一種分代收集的策略,即將堆分成3個不同的區(qū)域,按照對象存活的時間的不同,將對象保存在不同的堆上。在不同的代上面,應用不同的收集算法,來達到最優(yōu)化收集。
這3個區(qū)域叫年輕代,年老代和持久代。 除Class和Method等元數據在持久代上面分配以外,所有的對象都在年輕代上面分配,當達到一定的條件,如經過在年輕代上面N次收集后的對象,就會保存到年老代之中。
在年輕代上面進行的收集叫Minor collections,在年輕代和年老代上面同時收集叫Major collections。
Tips: 盡量不要調用System.gc(),因為這會觸發(fā)Major collections。Major collections的收集效率不高,因為它要遍歷幾乎所有的對象。 沒有辦法利用API直接觸發(fā)Minor collections,但是仍然有其他的調優(yōu)手段。當使用完集合對象后,把引用設置為null,這樣避免gc在收集過程中,無謂地遍歷那些即將就要釋放的對象。
實驗表明,年輕代上面的對象98%的對象都會在短時間內死亡,故Minor collections可以利用拷貝收集器,只遍歷那些2%存活的對象,而不用管那些死亡的對象,來提高收集效率。 為了讓Minor collections能充分利用年輕代上面對象大量死亡的這個特點,就需要調整以下幾個參數: 1. 收集的頻率 過于頻繁的收集,會導致代中對象死亡率不夠高,從而需要遍歷這個代中大部分的對象,使得高死亡率這個條件利用的不充分。
2. 年輕代(堆)的大小 如果堆太小,一旦堆被占滿,Minor collections就不得不頻繁的啟動,導致情況1的發(fā)生,從而降低收集效率。
下圖反應了絕大多數對象在早起死亡的這一個事實: 
從圖中可以看到,隨著時間的推進,大部分分配的字節(jié)都被回收了,少部分留了下來。被回收的這些字節(jié),就是所謂的die young,留下的則是live longer。
Bytes allocated 已經分配的總字節(jié)數 Bytes Surviving 存活的字節(jié)數 Minor Collections 對年輕代進行收集 Major Collections 對所有代進行收集 下圖描述了JVM中對堆的劃分: 
Young 年輕代 Tenured 年老代 Perm 持久代
Virutal是指保留,而未分配的內存。如Perm中,加上Virtual則是Perm區(qū)域最大的大小,而剛開始并不會完全分配這個堆,只會按照最小的大小分配。
Young部分,被分為了三個部分,一個Eden,和兩個大小相同的Survivor。 所有的新建對象都會在Eden中分配,當Eden占滿后,即剩余的大小不足以分配新的對象時,就會觸發(fā)Minor collections。對Young收集時,會將對象拷貝到其中一個Survivor,另外一個Survivor保留不用。當下次收集時,則將上次Survivor中和Eden中的活動對象,拷貝到未用的Survivor中。如此反復。當某些對象經歷足夠長的次數或者時間后,就會被拷貝入年老代。 如果對Young的minor collections收集到的活動對象,survivor無法完全容納,則會將某些對象拷貝到年老代,如果年老代也不能容納新拷貝入的對象,則觸發(fā)Major collections。如果Major collections后,如果還不足以容納,就會將Virutal中預留的空間用來擴展已有的堆。當保留Virutal分配完畢后,仍然不足時,就會拋出OutOfMemory的錯誤。
Tips: 不要把年老代設置的過小,一般最好能比eden+survivor更大一些,這樣可以避免觸發(fā)Major collections。在這個前提下,年輕代越大越好。由于Young的eden區(qū)域是拷貝收集,容易產生碎片,所以此區(qū)域越大,越不容易導致因為碎片導致的內存不足而引發(fā)的minor collections。至于survivor,則要根據情況調整。過大的survior會造成浪費,過小的survior會導致,對象被直接拷貝到年老代。 JVM1.2中,未使用上面一大二小的結構,而是將Young分成兩個相同大小的區(qū)域,來回進行拷貝。 調整GC的手段 
如何看懂上面那張圖: 行,分別對應了三個代 列,分別對應了實時性要求比較高的默認的和可調節(jié)的選項, 以及對吞吐量比較高的默認的和可調節(jié)的選項,還有就是調整這3個堆的選項。 
-Xmx 調整JVM啟動時,保留Total Size的大小 -Xms 調整JVM啟動初始化時,Total Size的大小
每一次收集后,都會根據以下兩個參數調整Total Size的大小
-XX:MinHeapFreeRatio(默認,40) Total Size中可用空間小于這個比率,就會擴展堆的大小,保持這個值 -XX:MaxHeapFreeRatio(默認,70) Total Size中可用空間大于這個比例,就縮小堆的大小,保持這個值 Tips: 如果你的程序是服務器,那么通過將-Xmx和-Xms設置成相近,或者相同,可以阻止這種堆大小的頻繁調整,造成的不必要的收集和堆增長過程。 -XX:NewRatio=3 年老代和年輕代的內存分配比率,3表示,在Total Size中,年輕代占1份,年老代3份 -XX:NewSize 年輕代初始化時的大小 -XX:MaxNewSize 如果不指定,那么年輕代可以增長不受限制,但受NewRatio的限制 -XX:SurvivorRato=6 年輕代中,eden與survivor的比率,這里eden占6份,2個survivor占2份 -XX:MaxTenuringThreshold=0 閥值,超過這個閥值的對象將被拷貝到年老代 如何進行GC調優(yōu)? 1.性能的幾個重要度量參數 Troughput 吞吐量,除掉GC所用的時間后,真正執(zhí)行程序所在總時間的百分比 Pauses 暫停時間,即由于正在做GC,而沒有響應的那些時間 Footprint 內存需求,通常用page和cache line的數量來衡量
2.查看當前的GC收集 java命令運行時,輸入 -verbose:gc 參數
[GC 4802K->4383K(5312K), 0.0078566 secs] //執(zhí)行了Minor collections [Full GC 4383K->4383K(5312K), 0.0385521 secs] //執(zhí)行了Major collections [GC 5248K->4814K(8216K), 0.0121798 secs] //空間仍然不夠,擴展了年輕代和年老代的空間 以第一行為例 GC 表示執(zhí)行了Minor collections 4802K->4383K 表示GC執(zhí)行前和執(zhí)行后,堆中活動對象的大小 (5312K) 表示總的堆的大?。ú凰愠志么?,而且只算2個Survivor中的1個,即用戶可用堆的大?。?br>0.0078566 secs 表示GC所用的時間。主要,也是首先看這個,然后再看GC/Full GC是否過于頻繁。 這個信息反應了什么?年輕代太小,因為執(zhí)行了Minor collections之后,活動的對象并沒有顯著減少,4802K->4383K, 說明初始堆分配的不夠大。這個GC顯然是因為eden內部碎片導致的。 用-XX:+PrintGCDetails 參數,打印更詳細的信息
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]] // Minor collections從年輕代64575K中收集到了959K的活動對象,花費了4微秒多一點 DefNew: 64575K->959K(64576K), 0.0457646 secs //整個堆整理后,從196016K中,收集到了133633K的活動對象 196016K->133633K(261184K) 還可以用-XX:+PrintGCTimeStamps 查看帶起始和終止時間戳的信息 還有-XX:-PrintTenuringDistribution 打印出對象在放入年老代之前在年輕代做了多少次復制,如果復制次數過少,說明年輕代過小。 2類收集器 The Throughput Collector 以提高吞吐量,降低GC時間比的收集策略 The Concurrent Low Pause Collector 以提高暫停時間,以實時性為目的的收集策略 具體請看資料[1]中的內容。 其他 關于,如何遍歷年輕代中的活動對象的技術,請看資料[3]。 參考資料:
1.Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine http://java./docs/hotspot/gc5.0/gc_tuning_5.html
2.Garbage Collector Ergonomics http://java./j2se/1.5.0/docs/guide/vm/gc-ergonomics.html
3.JVM1.4.1中的垃圾收集 http://www.ibm.com/developerworks/cn/java/j-jtp11253/
|