<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 42  文章 - 71  trackbacks - 0
    <2008年4月>
    303112345
    6789101112
    13141516171819
    20212223242526
    27282930123
    45678910

    常用鏈接

    留言簿

    隨筆檔案

    文章分類

    文章檔案

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    Technorati 標簽: ,,,

    Sun HotSpot JVM 1.4.2 調優

    目錄

    1. 序言

    2. 虛擬機中的"代"

    2.1. 性能考慮

    2.2. 測量

    3. 調整代大小

    3.1. 堆的總體大小

    3.2. 新生代

    3.3. 新生代的保證

    4. 收集器類型

    4.1. 何時使用吞吐收集器

    4.2. 吞吐收集器

    4.2.1. 大小自適應

    4.2.2. Aggressive堆

    4.2.3. 吞吐收集器的測量法

    4.3. 何時使用并發收集器

    4.4. 并發收集器

    4.4.1. 并發的額外開銷

    4.4.2. 新生代的保證

    4.4.3. 完全收集

    4.4.4. 漂浮垃圾

    4.4.5. 暫停

    4.4.6. 并發階段

    4.4.7. 并發收集的測量

    4.4.8. 并發收集器的并行次要收集

    4.5. 何時使用增量收集器

    4.6. 增量收集器

    4.6.1. 增量收集器測量

    5. 其他考慮事項

    6. 總結

    7. 其他文檔

    7.1. 輸出示例

    7.2. 常見問題


    1. 序言

    從小型桌面應用到運行在大型服務器的web服務,Java 2平臺被廣泛的應用。在1.4.1版本的J2SE平臺中,引入了2種新的垃圾收集器(garbage collector),現在總共有4種垃圾收集器供我們選擇。那么,應該如何選擇垃圾收集器?又有哪些因素可以作為選擇的依據?本文檔描述了垃圾收集器共有的特征,并對于在單線程,stop-the-world的收集器上如何最大限度的利用這些特征給出了調整指導。最后,討論了其他3種收集器獨有的特征以及在4種垃圾收集器中做出選擇的一些標準。

    對于用戶而言,在什么情況下,垃圾收集器會帶來性能問題?對于大部分應用來說,垃圾收集器不會帶來性能問題,也就是說,即使適垃圾收集器的運行會帶來很短的暫停,但是應用還是可以以令人滿意的性能運行。但是對于使用了大量的線程、處理器、Socket以及內存的大型應用而言(使用默認的垃圾收集器),情況就不同了。

    Amdahl 觀察到,大部分工作是無法很好的并行處理的,有些工作總是順序化的,所以,無法從并行機制中獲益。對于J2SE平臺來說,也是這樣,Java虛擬機從最初一直到1.3.1版本,都沒有并行的垃圾收集器,所以,相對于其他的并行應用,垃圾收集器所帶來的性能影響在多處理器系統上會有所增長。

    下圖描述的是一個具有良好彈性的理想系統,如果不考慮垃圾收集的話。紅線表示在單處理器系統上的應用,垃圾收集只占用1%的時間,當遷移到有32個處理器的平臺上的時候,有超過20%的吞吐量(throughput)的損失;在單處理器系統上垃圾收集時間占10%的應用(不考慮那些令人無法接受的垃圾收集時間),當擴展到32處理器系統上時,吞吐量的損失超過了75%。

    這表明了,在開發小型應用時可以忽略不計的速度問題,當擴展到大型應用系統時卻成為最為突出的性能瓶頸。但是對于這種瓶頸的小小的改良就可能收獲性能上的顯著提高,所以,對于大型應用來說,調整垃圾收集器是非常有價值的。

    對于大部分應用來說,默認的垃圾收集器都是可以滿足需要的。其他的幾種垃圾收集器因為有一些特殊的行為,使用起來比較復雜。除非應用有特殊需求,一般情況下建議選用默認的垃圾收集器。當然,也有例外情況,對于那些運行在有大量內存和處理器的主機上的大型應用,可以首先嘗試aggressive 堆(heap)選項(-XX:+AggressiveHeap),稍后有詳細描述。

    本文選用J2SE 1.4.2(Sun HotSpot),Sun Solaris操作系統(SPARC平臺版本),因為這個平臺對于硬件和軟件來說都有很好的擴展性。當然了,本文也適用于其他平臺,如 Linux,Microsoft Windows, Sun Solaris(X86平臺版本)。盡管在不同平臺上虛擬機的命令行參數是一樣的,但是可能其他平臺上的默認值和本文描述的有所差別。

    2. 虛擬機中的"代"

    J2SE平臺的一個特征就是它為開發人員實現了內存的申請和釋放,但是,當垃圾收集成為主要的性能瓶頸時,我們就有必要對它的實現進行深入的了解。垃圾收集器可以對應用使用對象的方式進行一個設定,反映在一些可調整的參數上,通過這些參數,在保證抽象能力的前提下提升了虛擬機的性能。

    從運行的程序中,沒有可以到達某一對象的指針,那么這個對象就被認為是"垃圾"。最直截了當的垃圾收集算法就是枚舉每個可到達的對象,剩下的對象就是可以進行回收的垃圾對象了。這種方案所消耗的時間和活動對象的數量是成比例的,所以,對于要保持大量活動對象的大型應用,顯然是不適用的。

    從J2SE 1.2開始,虛擬機將幾種不同的垃圾收集算法組合在一起使用,就是"分代收集"(generational collection)。在幼兒收集器(naive garbage collection)檢查堆中活動對象的同時,分代收集器通過分析一些觀測屬性來避免額外的工作。

    在這些觀測屬性中最重要的就是幼兒死亡率(infant mortality)。下面圖表中的藍色區域就是對象存活期的典型分布。其中X軸表示對象的存活時間,在分配字節時測量。Y軸表示的是對應存活時間的對象字節總量。左側的頂點表示對象可能在分配之后很短的時間內就被回收,例如在循環中的枚舉對象,僅僅能夠存活一個循環。

    有些對象能夠存活很長時間,所以上圖的X軸向右延展。例如那些在初始化時就創建并且能夠存活直到進程終止的對象。在這兩個極端之間,就是一些只在中間計算才存活的對象,就是我們看到的到幼兒死亡率右側的區域??赡苡行玫膶ο蟠婊钪芷诜植加行┎町悾钊梭@訝的是大部分應用都和上圖吻合。通過關注主要的"夭折"(die young)對象來進行垃圾收集是行之有效的。

    為了針對這種情況進行優化,所以將內存進行分代管理,也就是說內存池保持不同年齡段的對象。在分代管理的內存中,當一個代被對象充滿的時候,就在這個代上進行垃圾收集。對象是在新生代(young generation)區域中進行分配,因為大部分對象在這個代中就已經死亡。當新生代被填充滿之后,在其上進行一次次要垃圾收集(minor collection),由于在新生代有很高的夭折率,所以次要收集算法針對這種情況進行了優化。這種收集的消耗和被其收集的對象的數量成比例。充滿死亡對象的新生代收集起來非常的快。一些仍然存活的對象被移動到舊生代(tenured generation),當舊生代需要進行垃圾收集的時候,就會進行主要垃圾收集(major collection),這種收集方式比起次要收集來要慢得多,因為它涉及了全部的活動對象。

    下圖顯示,次要收集的間隔時間比較長,這樣能夠確保大部分對象都已經死亡。為了確保次要收集發生的間隔能夠足夠長,需要新生代有足夠的空間,這樣才能夠使得次要收集充分利用新生代中高夭折率的特點。對于那些對象存活周期分布比較奇特的應用情況就不同了,還有,不合適的代大小設置也會使得在對象死亡之前就不得不進行收集。

    默認的垃圾收集器可以應用在小型的和大型的應用,但是它的默認參數設置對于小型應用更高效。對于很多服務器應用來說,這些參數并不合適,這就引出了本文的中心原則:

    如果垃圾收集成為性能瓶頸,建議你調整代的大小參數。并檢查垃圾收集的輸出信息,研究性能對于垃圾收集參數的敏感度

    默認的代配置如下圖所示:

    在初始化時,虛擬機保持一個很大的地址空間,但并不真正的從物理內存中申請(按需申請)。用來存放對象的地址空間被劃分成兩部分:新生代和舊生代。

    新生代由一個Eden和兩個存活空間(survivor space)組成,對象在Eden中進行創建和內存分配。在任何時候,都有一個存活空間是空的,以用來容納從Eden和另外一個存活空間復制過來的活動對象。對象以這種方式在兩個存活空間之間進行復制,直到它存活時間較長而被復制到舊生代。

    包括1.2版本的虛擬機(Solaris操作系統)在內的其他虛擬機產品,都是使用兩個大小相等的存活空間來進行對象復制,而不是像圖中所示的一個Eden和兩個存活空間。這就意味著,通過調整新生代大小來調整性能并不總是具有可比性的。

    在舊生代中,有一部分比較特殊的,叫做持久代(permanent generation),它用來存放虛擬機自己的數據,例如(class)和方法(method)。

    2.1. 性能考慮

    對于垃圾收集的性能,主要有兩個指標。吞吐量(throughput)指用來進行垃圾收集之外工作所用的時間占總時間的百分比,一般要通過長時間的觀察和測量。吞吐量包括了分配內存所花費的時間在內(一般來說無需對分配進行調優)。暫停(Pause)指由于進行垃圾收集而導致應用無法響應的時間。

    用戶對于垃圾收集有不同的需求,例如,對于Web服務器應用來說,吞吐量是要著重考慮的,而暫停時間可能由于網絡的反應時間而不那么明顯;而對于一個交互式圖形界面的應用來說,即使是短暫的暫停都會帶來非常不好的用戶體驗。

    通常來說,如何設置代的大小是在這些考慮因素之間作出的一個權衡。例如,將新生代設置得很大會得到很好的吞吐性能,但是會增加暫停時間;反之,較小的新生代設置會減小暫停時間,但是降低了吞吐量。一個代的大小不應該影響在其他代上進行垃圾收集的頻率和暫停時間。

    對于代的大小設置,沒有一個精確的計算方法。同時考慮應用對內存的使用特征以及用戶對垃圾收集的需求,才能夠做出最好的選擇。因此,虛擬機默認的垃圾收集的相關參數可能不是最優的,需要通過用戶定制這些命令行選項加以調整。

    2.2. 測量

    對于特定的應用來說,吞吐量和內存占用還是比較容易測量的。例如對于一個web服務應用,可以使用壓力測試工具來測量它的吞吐量,內存占用可以使用Solaris操作系統提供的命令pmap來測量。另外,通過打開虛擬機的詳細診斷信息來估算垃圾回收的暫停時間。

    通過使用虛擬機選項-verbose:gc,能夠將每次收集時的一些信息打印出來。需要注意的是,對于不同版本的J2SE平臺來說,-verbose:gc的輸出格式可能稍有差異。下面的示例是一個大型的服務器應用的-verbose:gc輸出。

    [GC 325407K->83000K(776768K), 0.2300771 secs]

    [GC 325816K->83372K(776768K), 0.2454258 secs]

    [Full GC 267628K->83769K(776768K), 1.8479984 secs]

    上面的輸出中,表明虛擬機進行了兩次次要收集和一次主要收集。箭頭兩端的數字

    325407K->83000K(第一行)

    表示在進行垃圾收集之前和之后活動對象的總大小。在次要收集之后的數字(83000K)包含了那些并不是必要活動的對象,但是還不能被回收。因為這些對象可能本身是活動的,或者有來自舊生代的引用。括弧中的數字

    (776768K)(第一行)

    表示總共的可用空間的大小。這個大小不包括持久代在內,是堆的大小減去一個存活空間的大小。此次次要收集大約用了1/4秒

    0.2300771 secs (第一行)

    主要收集的輸出格式和第三行相似。如果使用了-XX:+PrintGCDetail選項,就會將垃圾收集時的詳細信息打印出來。同樣,對于不同的J2SE平臺版本,根據虛擬機的需要,-XX:+PrintGCDetail的輸出格式也有所不同。下面就是在J2SE平臺1.4.2版本上使用-XX:+PrintGCDetail參數的輸出示例:

    [GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]]

    從上面的輸出信息可以看出,次要收集釋放了新生代大約98%的空間,

    DefNew: 64575K->959K(64576K)

    用了大約46毫秒,

    0.0457646 secs

    整個堆空間的使用率下降到51%左右

    196016K->133633K(261184K)

    最后看到,相對于新生代的收集,在時間消耗上稍微多了一點點,

    0.0459067 secs

    選項-XX:+PrintGCTimeStamps在輸出垃圾收集信息的時候帶有時間戳。

    111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]

    收集在111秒的時候開始,幾乎同時次要收集也開始了。對于新生代上的主要收集也有了一些附加信息,新生代的使用率下降到10%

    18154K->2311K(24576K)

    用了大約0.13秒

    0.1293306 secs

    3. 調整代大小

    很多參數都能夠影響代的大小,下圖表示的就是約定空間(committed space)和虛擬空間(virtual space)之間的不同。在虛擬機初始化的時候,整個空間都為堆保留??梢酝ㄟ^-Xmx參數指定整個空間的大小,如果-Xms參數的值比-Xmx小,那么在一開始,堆所保留的空間可能到不了-Xmx參數指定的值,那么未保留的這部分空間就被表示為虛擬的。在需要更大的空間的時候,堆的不同部分(新生代,舊生代,持久代)都可能增長直到其上限。

    有些參數指定的是堆中一部分空間和另外一部分的大小比率,例如,NewRatio參數表示舊生代和新生代的比率。稍后會有關于這些參數的討論。

    3.1. 堆的總體大小

    當某個代被充滿的時候,就會發生垃圾收集,所以,吞吐量和可用內存的大小成反比,總內存大小是影響垃圾收集的最重要因素。

    默認情況下,虛擬機總是試圖收縮內存大小,來保證每個代內的活動對象所占用的空間的比率維持在特定范圍,這個范圍就是由參數-XX:MinHeapFreeRatio=<最小值>和-XX:MaxHeapFreeRatio=<最大值>以及-Xms和-Xmx來確定。在Solaris操作系統平臺(SPARC版本)如下表所示:

    -XX:MinHeapFreeRatio=

    40

    -XX:MaxHeapFreeRatio=

    70

    -Xms

    3670k

    -Xmx

    64m

    如果使用默認值,那么在某個代中,當空閑空間低于40%,那么虛擬機就會增長這個空間以使得空閑空間能夠保持在40%以上,假設這個代的大小未超過最大限制。類似的,如果代中空閑空間的大小超過了70%,虛擬機就會收縮這個代的大小,使空閑比率降到70%以下,當然了,要高于40%。

    根據經驗,這些默認的參數設置不適和大型的服務器應用,一個問題就是啟動緩慢,因為堆的初始大小比較小,不得不進行多次的主要收集來釋放空間,另外一個問題就是-Xmx參數的默認設置對于這些服務器應用來說是很不合理的,所以,對于服務器應用,有這樣的指導規則:

    · 除非在垃圾收集的時候暫?,F象嚴重,否則要盡可能多的給虛擬機分配內存,64M通常都太小了

    · 設置-Xms和-Xmx一樣大,以避免虛擬機在收縮大小時的消耗。另外,如果你做出了糟糕的選擇,虛擬機無法補償

    · 當增加了處理器之后,要相應的增加虛擬機內存大小,因為內存分配是可以并行進行的

    關于虛擬機的全部參數的解釋,可以參考:

    http://java.sun.com/docs/hotspot/VMOptions.html

    3.2. 新生代

    第二個重要的影響因素是虛擬機中新生代的大小。新生代越大,次要回收發生的次數就越少。當然,對于一定大小的堆來說,新生代越大,就意味著舊生代越小,那么就會使得主要回收的次數增多。最佳選擇應該參考應用創建的對象的存活周期來設定。

    默認情況下,新生代的大小是由NewRatio參數來設置的,例如設置-XX:NewRatio=3表示新生代和舊生代之間的比率為1:3,就是說Eden和存活空間總共占堆大小的1/4。

    參數NewSize和MaxNewSize指定了新生代大小的范圍,設定這個參數就意味著限定了新生代的大小,就像使用-Xms和-Xmx參數限制堆的大小一樣。最好將新生代的大小設定為經過NewRatio參數計算出的大小的整倍數。

    3.3. 新生代的保證

    理想的次要收集會將活動對象從新生代的一部分(Eden和第一個存活空間)復制到另外一部分(第二個存活空間)。但是,無法保證第二個存活空間能夠容納全部活動對象。所以,在舊生代一定要有足夠的空閑空間來容納全部的活動對象。最糟糕的情況就是在舊生代的空閑空間為Eden大小加上非空存活空間的大小。當舊生代也沒有足夠的空閑空間的時候,就需要進行一次主要收集。對于小型應用來說,這種策略是可以滿足需要的,因為在舊生代的內存主要是虛擬約定的,并未真正使用。但是對于需要很大堆內存的服務器應用來說,大于堆虛擬約定內存一半的Eden大小是沒有任何意義的:只會發生主要收集。需要指出的是,在新生代能夠使用除"吞吐收集器"(throughput collector)之外的其他類型的收集器。如果舊生代無法容納新生代復制過來的活動對象,吞吐收集器會在這個兩個代上都進行垃圾收集。

    如果有特殊需要,使用參數SurvivorRatio可以調整存活空間的大小,但是通常這個參數對性能的影響不那么重要。例如,-XX:SurvivorRatio=6就設定了每個存活空間和Eden的比率為1 :6,換句話說,就是每個存活空間占新生代大小的1/8(不是1/7,因為有兩個存活空間)。

    如果存活空間設置的太小,就會導致過于頻繁的向舊生代復制對象;如果存活空間設置的太大,就會因為空閑而白白浪費。虛擬機的垃圾收集器都會為對象在復制到舊生代之前的反轉復制次數選擇一個閥值,通過設定這個閥值來保證存活空間一直處于半滿狀態。通過指定-XX:+PrintTenuringDistribution參數可以觀察到這個閥值,以及新生代中對象的"年齡"。同時,這個參數對于觀察應用產生對象的存活期分布是很有幫助的。

    下面是Solaris操作系統平臺(SPARC版本)上一些參數的默認值:

    NewRatio

    2(client虛擬機是8)

    NewSize

    2228k

    MaxNewSize

    無限

    SurvivorRatio

    32

    新生代的最大大小是根據堆的總大小以及NewRatio來計算出來的,默認的"無限"表示:除非在命令行中指定了MaxNewSize,否則該計算出來的值不限制MaxNewSize的大小。

    對于服務器應用,有這樣的指導規則:

    · 首先確定能夠給虛擬機使用的最大內存,然后通過調整新生代的大小來找到滿足性能需求的最佳值

    · 除非發現性能瓶頸在于頻繁的主要收集或者暫停時間,盡可能多的給新生代分配內存

    · 將新生代大小設置到接近堆大小的一半會適得其反

    · 當增加了處理器之后,要相應的增加新生代大小,因為內存分配是可以并行進行的

    4. 收集器類型

    到目前為止,我們還只是針對默認的垃圾收集器展開討論。從J2SE平臺1.4.2版本開始,加入了另外3種垃圾收集器,都是著重提高吞吐能力,降低垃圾收集時的暫停時間。

    1. 吞吐收集器(throughput collector):命令行參數:-XX:+UseParallelGC。在新生代使用并行收集策略,在舊生代和默認收集器相同。

    2. 并發收集器(concurrent low pause collector):命令行參數:-XX:+UseConcMarkSweepGC。在舊生代使用并發收集策略,大部分收集工作都是和應用并發進行的,在進行收集的時候,應用的暫停時間很短。如果綜合使用-XX:+UseParNewGC和-XX:+UseConcMarkSweepGC,那么在新生代上使用并行的收集策略。

    3. 增量收集器(incremental low pause collector):命令行參數:-Xincgc。使用增量收集器要謹慎,他只是在每次進行次要收集的時候對舊生代進行一部分的收集,這樣就把主要收集所帶來的較長時間的停頓分散到多次的次要收集。但是,考慮到總共的吞吐,可能比舊生代上默認的收集還要慢。

    注意,-XX:+UseParallelGC和XX:+UseConcMarkSweepGC不能同時使用。對于J2SE1.4.2版本會檢查垃圾收集相關參數組合的合法性,但是對于之前的版本沒有這個檢查,可能會導致不可預知的錯誤。

    在嘗試使用其他的收集器之前,建議先使用默認的收集器,通過調整代的大小以及相關參數來看看哪些方面不能滿足需求,通過以上信息提供的參考,再決定選擇其他的收集器。

    4.1. 何時使用吞吐收集器

    當你的應用運行在多個處理器的主機上時,考慮使用吞吐收集器。因為默認的收集器是由一個線程來完成收集工作的,因此會給應用增加串行執行時間。吞吐收集器是多線程的進行次要收集的,所以可以降低應用的串行執行時間。一種典型的情況就是應用中有很多線程都在創建對象。這種情況下也需要增大新生代的大小。

    4.2. 吞吐收集器

    吞吐收集器和默認的收集器類似,都是分代收集器。不同之處就在于吞吐收集器用多線程進行次要收集,主要收集本質上和默認收集器相同。默認情況下,在N個CPU的主機上,就會啟動N個收集線程,收集線程的數量可以通過命令行選項進行控制。在只有1顆CPU的主機上,吞吐收集器的性能表現可能還不如默認收集器,因為一些并行執行(例如進行同步時的開銷)帶來了額外的開銷;在2顆CPU的主機上,吞吐收集器和默認收集器性能相當;在多于2顆CPU的主機上,你會發現進行次要收集的暫停時間降低了。

    通過命令行參數-XX:+UseParallelGC來指定使用吞吐收集器,ParallelGCThreads參數用來設置線程數量(-XX:ParallelGCThreads=<具體數值>)。吞吐收集器對堆內存的需求和默認收集器一樣。使用吞吐收集器能夠降低在新生代上進行次要收集時的暫停時間,但是由于有多個線程參與次要收集,在從新生代向舊生代提升(promotion)的時候可能會產生碎片。為了進行對象從新生代到舊生代的提升,每個垃圾收集線程都會保留一塊舊生代的空間,舊生代就被劃分成多個"提升緩沖"(promotion buffers),這樣就容易產生內存碎片。減少收集線程的數量能夠減少碎片的產生,同時會增加舊生代的大小。

    4.2.1. 大小自適應

    從J2SE平臺1.4.1版本開始,吞吐收集器就具有了一個特征,就是大小自適應(參數-XX:+UseAdaptiveSizePolicy),這個選項默認是打開的。該特征對于收集時間、分配比例、收集之后堆的空閑空間等數據進行統計分析,然后以此為依據調整新生代和舊生代的大小以達到最佳效果??梢允褂?verbose:gc來查看堆的大小。

    4.2.2. Aggressive

    -XX:+AggressiveHeap選項會檢測主機的資源(內存大小、處理器數量),然后調整相關的參數,使得長時間運行的、內存申請密集的任務能夠以最佳狀態運行。該選項最初是為擁有大量內存和很多處理器的主機而設計的,但是從J2SE1.4.1以及其后繼版本來看,即使是對于那些只有4顆CPU的主機,該選項都是很有幫助的。因此,吞吐收集器(-XX:+UseParallelGC)、大小自適應(-XX:+UseAdaptiveSizePolicy)以及本選項(-XX:+AggressiveHeap)經常結合在一起使用。要使用本選項,主機上至少要有256M的物理內存,堆內存的最初大小是基于物理內存計算出來的,然后會根據需要盡可能的利用物理內存。

    4.2.3. 吞吐收集器的測量法

    -verbose:gc的使用和默認收集器相同。

    4.3. 何時使用并發收集器

    如果你的應用在運行的時候能夠擁有足夠的處理器資源,并且減小垃圾收集的暫停時間能夠明顯提升應用性能,此時考慮使用并發收集器。例如那些有大量相對長生存期的對象(較大的舊生代)的應用,并且運行在有2顆甚至更多的CPU的主機上。但是實際上這種收集器是為那些需要短暫停時間的應用所設計的。運行在單處理器上的交互式應用,如果設置一個合適的舊生代大小,能夠達到非常良好的效果。

    4.4. 并發收集器

    并發收集器和默認收集器類似,都是分代收集器。并發收集器在舊生代上并發進行垃圾收集。

    并發收集器旨在降低舊生代上進行收集的暫停時間,他使用一組互相隔離的收集線程,每個線程負責一部分的主要收集,這個收集過程和應用的運行是并發進行的。通過命令行選項-XX:+UseConcMarkSweepGC來使用并發收集器。每當發生主要收集的時候,并發收集器在收集開始的時候和中期會短時間的暫停所有的應用線程,中期的暫停時間相對長一點,在這次暫停中,多個線程同時工作完成收集任務。剩余的收集工作將由一個收集線程完成,這次是和應用并發執行的。次要收集的過程和默認收集器類似,也可以使用多線程的方式完成次要收集,參看"并發收集器的并行次要收集"章節。

    在下面連接中對并發收集器(針對舊生代)中用到的技術進行了詳細的闡述:

    http://research.sun.com/techrep/2000/abstract-88.html

    4.4.1. 并發的額外開銷

    雖然并發收集器有效的降低了收集時的暫停時間,但卻是以使用更多的處理器資源為代價的。收集過程中并發進行的那一部分是由單一的一個線程完成的。對于一個在N個處理器上運行的系統,并發進行的那部分會使用1/N的處理器資源??赡茉趩翁幚砥鞯南到y上你看到他表現也還可以,那只是偶然罷了。當然,它能夠將一次長時間的暫停(這里的暫停指所有的應用線程都不可用了)分割成多個短時間的暫停,但是這個并不是它的設計初衷。同時,并發收集會有額外的開銷,并且可能降低系統吞吐能力,同時對于某些類型的應用來說,并發收集是有先天缺陷的(例如容易造成內存碎片)。在擁有2顆處理器的系統上,并發收集在執行的時候,還有1顆處理器可以為應用服務,因此,執行收集不會"暫停"應用的運行。雖然降低了暫停時間,但是并發收集確實占用了一部分處理器資源,所以你可能感覺到應用會有所緩慢。N的值越大,在并發收集上所用的處理器資源就越少,并發收集器的優勢就越明顯。

    4.4.2. 新生代的保證

    如果使用默認收集器,那么在進行次要收集的時候,必須保證舊生代中有足夠的空間來容納從Eden和存活空間復制過去的對象。由于并發收集會產生內存碎片,所以這個保證的條件就更加苛刻:在舊生代必須要有足夠的連續空間來容納來自Eden和一個存活空間的對象,因為沒有什么方式能夠準確知道Eden和這個存活空間中對象大小的分布情況(主要是為了避免巨大的性能消耗)。相對于默認收集器,并發收集器往往需要更大的堆內存。在默認收集器時,堆內存需要保留但是不一定真正的使用。先使用默認收集器,找到一個新生代和舊生代大小的合適的估算值,然后將舊生代的大小設置成和新生代一樣大再去使用并發收集器。這只是一個非常粗略的近似值,至于實際上的最佳設置是由應用來決定的。

    4.4.3. 完全收集

    在舊生代被填滿之前,并發收集器使用一個收集線程來完成收集工作,同時不暫停應用的執行。實際上,即使應用的線程都在運行,收集器都可以并發來做更多的工作,所以,對于應用而言,只會感覺到非常短暫的暫停。但是當舊生代在填滿之前如果收集工作無法全部完成,那么就會暫停應用的線程來完成所有的收集工作。這就是我們所說的完全收集(full collections)。如果發現完全收集比較頻繁,可能需要調整并發收集的相關參數。

    4.4.4. 漂浮垃圾

    垃圾收集的工作就是查找堆中的所有活動對象。當應用的線程和垃圾收集的線程并發執行的時候,那么對于收集線程來說當時是活動的對象可能在收集工作完成之后就變成了非活動對象。這就是所說的"漂浮垃圾"(floating garbage)。漂浮垃圾的數量和并發收集的時間有關(應用線程需要花一些時間才丟棄對象),和應用的細節也有關系??梢酝ㄟ^增加舊生代20%(這個是估計值)解決漂浮垃圾問題。當然了,在下一輪收集的時候,這些垃圾都將被收集掉。

    4.4.5. 暫停

    在一次并發收集周期,需要兩次暫停應用。第一次暫停時,標識所有的可以從根對象(例如線程棧,靜態對象等)或者堆中的其它對象(例如新生代)直接到達的對象,這就是"初始標識"(initial mark)。緊接著就是第二次暫停,此次暫停的目的是為了找出由于收集和應用的并發執行而疏漏的、未被標識對象,這叫做"重新標識"(remark)。

    4.4.6. 并發階段

    在初始標識和重新標識之間有一個并發標識的過程,在并發標識的時候,收集線程需要占用一部分本來屬于應用的處理器資源。在重新標識階段之后,還有一個并發清理過程,同樣,也會占用一部分處理器資源。在清理階段之后,并發收集線程進入休眠狀態,直到下一輪主要收集的發生。

    4.4.7. 并發收集的測量

    下面是使用了-XX:+PrintGCDetails參數的-verbose:gc輸出(已經刪除了一些詳細信息),我們可以看到并發收集的輸出中穿插了很多次要收集,一般來說,一個并發收集周期中,會有多次的次要收集。CMS-initial-mark表示并發收集周期的開始,CMS-concurrent-mark表示并發標識階段的結束,CMS-concurrent-sweep表示并發清理階段的結束。我們之前沒有討論的預清理階段(precleaning phase)由CMS-concurrent-preclean標識,它表示的是可以并發執行的一些工作并且是為重新標識階段(CMS-remark)做好了準備。最后一個階段由CMS-concurrent-reset標識,表示已經為下一輪的并發收集做好了準備。

    [GC [1 CMS-initial-mark: 13991K(20288K)] 14103K(22400K), 0.0023781 secs]

    [GC [DefNew: 2112K->64K(2112K), 0.0837052 secs] 16103K->15476K(22400K), 0.0838519 secs]

    ...

    [GC [DefNew: 2077K->63K(2112K), 0.0126205 secs] 17552K->15855K(22400K), 0.0127482 secs]

    [CMS-concurrent-mark: 0.267/0.374 secs]

    [GC [DefNew: 2111K->64K(2112K), 0.0190851 secs] 17903K->16154K(22400K), 0.0191903 secs]

    [CMS-concurrent-preclean: 0.044/0.064 secs]

    [GC[1 CMS-remark: 16090K(20288K)] 17242K(22400K), 0.0210460 secs]

    [GC [DefNew: 2112K->63K(2112K), 0.0716116 secs] 18177K->17382K(22400K), 0.0718204 secs]

    [GC [DefNew: 2111K->63K(2112K), 0.0830392 secs] 19363K->18757K(22400K), 0.0832943 secs]

    ...

    [GC [DefNew: 2111K->0K(2112K), 0.0035190 secs] 17527K->15479K(22400K), 0.0036052 secs]

    [CMS-concurrent-sweep: 0.291/0.662 secs]

    [GC [DefNew: 2048K->0K(2112K), 0.0013347 secs] 17527K->15479K(27912K), 0.0014231 secs]

    [CMS-concurrent-reset: 0.016/0.016 secs]

    [GC [DefNew: 2048K->1K(2112K), 0.0013936 secs] 17527K->15479K(27912K), 0.0014814 secs]

    初始標識的暫停相對于次要收集的暫停要短,反之,并發階段(并發標識,并發預清理,并發清理)的暫??赡芟鄬σL一些,但是在收集的過程中,應用不會有任何暫停。而由于標識所引起的暫停和應用的細節(例如頻繁的修改對象會增加暫停時間)以及上一次次要收集的時間(例如新生代中有大量的對象時也會增加暫停時間)有關。

    4.4.8. 并發收集器的并行次要收集

    在多處理器平臺上,可以使用參數UseParNewGC來降低次要收集的暫停時間:

    -XX:+UseParNewGC

    如果使用了UseParNewGC,那么同時使用CMSParallelRemarkEnabled參數可以降低標識暫停:

    -XX:+CMSParallelRemarkEnabled

    4.5. 何時使用增量收集器

    如果你的應用可以用較頻繁的、較長時間的新生代上收集來換取稍短時間的舊生代收集,就可以考慮使用增量收集器。典型情況是如果需要長時間的舊生代收集時(大量的長存活期對象),小型的新生代收集也能夠滿足(大部分對象是短存活期的),并且只有一個處理器。

    4.6. 增量收集器

    同樣,增量收集器也是和默認收集器類似的分代收及器,在新生代上的次要收集和默認收集器一樣。不要在使用增量收集器的同時使用-XX:+UseParallelGC或者-XX:+UseParNewGC。在舊生代上的主要收集是增量完成的。

    這種收集器在每次做次要收集的時候,進行一部分的主要收集,這樣就避免了做完整的主要收集所帶來的長時間暫停。但是有時候為了避免出現內存溢出(out of memory)的問題,也會在舊生代上進行完整的主要收集(就象默認收集器那樣)。

    由于這種收集器會在堆內存上產生碎片,所以相對于默認的標識-清理-壓縮(mark-sweep-compact)收集器來說,可能需要更大一些的堆內存。

    為了能夠在每次次要收集時進行一部分的主要收集,收集器需要維護一些附加信息,所以,增量收集的總體消耗要高一些,并且吞吐可能不如默認收集器那么好。

    首先使用默認收集器找到一個合適的堆大小,如果此時主要收集的暫停時間還是無法滿足應用需求,嘗試調整各個代的大小,并且使用增量收集器,直到找到合適的堆設置。

    · 如果在使用增量收集器的時候發生了完全收集(full collection),可能在舊生代發生內存溢出之前無法完成增量的垃圾收集,這個時候,你需要減小新生代的大小,以迫使次要收集發生頻率更高一些。

    · 如果由于無法滿足新生代保證而發生的主要收集,那么會產生內存碎片。一次次要收集沒有能夠回收任何空間,此時表明無法保證新生代需求了,此時嘗試增大舊生代大小來彌補碎片問題,可能不會真正使用很大的舊生代,但是對于新生代保證來說是有幫助的。

    4.6.1. 增量收集器測量

    將-verbose:gc和-XX:+PrintGCDetail組合使用,可以看到如下的輸出:

    [GC [DefNew: 2074K->25K(2112K), 0.0050065 secs][Train: 1676K->1633K(63424K), 0.0082112 secs] 3750K->1659K(65536K), 0.0138017 secs]

    從上面的輸出可以看出,進行次要收集用時大約5毫秒,同時還有一次增量收集(Train:…),用時大約8毫秒。如果發生了完全收集,在輸出中會看到Train:MSC字樣:

    [GC [DefNew: 2049K->2049K(2112K), 0.0003304 secs][Train MSC: 61809K->357K(63424K), 0.3956982 secs] 63859K->394K(65536K), 0.3987650 secs]

    同時,從上面的輸出中可以看出,次要收集并沒有起到作用:收集前后都是2049K,這就表明了在舊生代上沒有連續的空間能夠滿足新生代保證。

    5. 其他考慮事項

    對于大部分應用來說,持久代的大小不會影響垃圾收集的性能。但是有些應用會動態的產生或者加載大量的對象,例如JSP的容器,如果需要,可以使用參數ManPermSize來增加持久代的大小。

    有些應用的finalization或者弱引用/軟引用/幻影引用(weak/soft/phantom refrences)和垃圾收集相互影響。這種特點可能從Java語言本身就造成了很差的垃圾收集性能,一個典型的例子就是依賴對象的finalize方法來釋放資源,比如關閉文件描述符(file descriptor),這樣就極大地影響了垃圾收集的性能。無論如何,依賴垃圾收集來釋放資源都是非常不可取的方式。

    應用影響垃圾收集器的另外一種方式就是顯式的調用垃圾回收,例如調用System.gc()方法。這個調用強制進行主要收集,對于大型應用的可擴展性有很大的傷害??梢允褂脜?XX:+DisableExplicitGC來禁止應用顯式的調用垃圾收集。

    另外就是在RMI分布式垃圾收集(RMI distributed garbage collection, DGC)時經常遇到使用顯式的垃圾收集,應用通過使用RMI引用在另外一個Java虛擬機中的對象,在這種分布式應用中,垃圾對象無法通過傳統的垃圾收集進行清理,所以RMI強制進行周期性的垃圾收集??梢酝ㄟ^一些屬性來設置收集周期:

    java -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 ...

    默認的收集周期是1分鐘,上面的參數指定的周期為1小時。但是,這樣設置可能會使得某些對象要經過很長時間才能夠被回收。如果對于DGC沒有時間上的需求限制,可以設置為Long.MAX_VALUE。

    Solaris 8操作系統平臺使用另外一個版本的線程庫(libthread)能夠將線程綁定為輕量進程(light-weight process, LWP),對于一些應用來說可能這個版本的線程庫比較有益處。要使用這個線程庫,在啟動Java虛擬機時在環境變量LD_LIBRARY_PATH中包含/usr/lib/lwp。在Solaris 9上,這個線程庫是默認的。

    相對于客戶端模式的虛擬機(-client選項),當使用服務器模式的虛擬機時(-server選項),對于軟引用(soft reference)的清理力度要稍微差一些??梢酝ㄟ^增大-XX:SoftRefLRUPolicyMSPerMB=1000來降低收集頻率。默認值是1000,也就是說每秒一兆字節。

    6. 總結

    根據應用的需求不同,垃圾收集可能會成為性能的瓶頸。如果充分了解應用的需求,并且深入理解垃圾收集的機制以及相關選項,能夠將垃圾收集對性能的影響降至最小。

    7. 其他文檔

    7.1. 輸出示例

    GC輸出示例列出了不同類型的垃圾收集的行為,以及對于垃圾收集詳細信息的診斷,并描述了如何來分析問題。

    http://java.sun.com/docs/hotspot/gc1.4.2/example.html

    7.2. 常見問題

    對于常見問題的一些解答,比本文檔要詳細一些。

    http://java.sun.com/docs/hotspot/gc1.4.2/faq.html

    原文出處:http://java.sun.com/docs/hotspot/gc1.4.2/

    posted on 2008-04-14 00:21 YODA 閱讀(1779) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 国产亚洲精品xxx| 亚洲国产精品久久久久网站| 亚洲中文字幕一二三四区苍井空| 日韩精品内射视频免费观看 | 男女超爽刺激视频免费播放 | 男人j进入女人j内部免费网站| 浮力影院亚洲国产第一页| 久久久久免费视频| 在线a亚洲v天堂网2019无码| 国产亚洲精品免费视频播放| 亚洲成人精品久久| 免费观看黄色的网站| 亚洲中文无码mv| 免费jjzz在在线播放国产| caoporm超免费公开视频| 国产成人亚洲精品青草天美| 亚洲成人在线免费观看| 亚洲首页国产精品丝袜| 国产男女猛烈无遮档免费视频网站 | 亚洲综合精品伊人久久| 四虎免费大片aⅴ入口| 杨幂最新免费特级毛片| 亚洲AV无码欧洲AV无码网站| **aaaaa毛片免费| 亚洲精品无码专区久久| 久久久久亚洲AV成人网人人软件| 一级**爱片免费视频| 亚洲va久久久噜噜噜久久狠狠| 131美女爱做免费毛片| 亚洲欧美aⅴ在线资源| 国产亚洲一区区二区在线| 99视频免费播放| 亚洲av日韩av永久在线观看| 中文字幕精品亚洲无线码一区应用| 91久久精品国产免费直播| 亚洲av成人一区二区三区在线播放 | 中文字幕亚洲情99在线| 亚洲成av人在片观看| 国产免费久久精品99久久| 亚洲国产精品综合久久2007| 免费一看一级毛片|