自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java SE 6 Hotspot虛擬機(jī)垃圾回收調(diào)優(yōu)

開發(fā) 后端
譯者按:有些術(shù)語(yǔ)實(shí)在很別扭,不過譯者文采有限,沒法找到合適的中文詞匯,不太影響理解,湊合看吧。

譯者按:有些術(shù)語(yǔ)實(shí)在很別扭,不過譯者文采有限,沒法找到合適的中文詞匯,不太影響理解,湊合看吧。

1. 概述

Java 平臺(tái)標(biāo)準(zhǔn)版(Java SE™)被廣泛應(yīng)用于各種應(yīng)用,從桌面上的小小的 applet 到大型服務(wù)器上的 Web Service 無(wú)處不在。為了支持各種不同的部署場(chǎng)景,Java HotSpot™ 虛擬機(jī)提供了多種垃圾回收器,每種都為滿足不同的需求而設(shè)定。這是也為了滿足大大小小不同應(yīng)用需求的一部分。不過,那些需要高性能應(yīng)用的用戶、開發(fā)者和管理員們也被選擇適合他們應(yīng)用的恰當(dāng)?shù)睦厥掌鞯姆爆嵗_著。取消這些額外操作的重要一步是在 J2SE™ 5.0 中作出的:垃圾回收器會(huì)根據(jù)應(yīng)用運(yùn)行的計(jì)算機(jī)類型而作出選擇。

這個(gè)垃圾回收器的“更好的選擇”總的說是一種進(jìn)步,不過,這并不意味著對(duì)所有的應(yīng)用這都是最好的選擇。對(duì)于有極端的性能或其他需求的用戶,仍需要顯式地指定垃圾回收器,并調(diào)優(yōu)某些參數(shù),以達(dá)到滿意的性能。本文就為這些需求提供了一些相關(guān)信息。首先,本文會(huì)基于串行的 stop-the-world 垃圾回收器來介紹垃圾回收器的一般性特征和基本調(diào)優(yōu)開關(guān)。接下來會(huì)介紹其他垃圾回收器的特點(diǎn)和如何選擇一個(gè)垃圾回收器。

何時(shí)選擇垃圾回收器?對(duì)于一些應(yīng)用,這個(gè)答案可能是“永遠(yuǎn)不”。也就是說,在有低頻率、短時(shí)的垃圾收集器造成的停頓的情況下,大部分程序都運(yùn)行良好。不過,這并不適用于很多程序,特別是那些處理大量數(shù)據(jù)(若干GB)、很多線程和需要處理很多事務(wù)的情況。

Amdahl 觀察到,大部分工作負(fù)載并不能被很好的并行化;有部分情況下總是會(huì)被順序執(zhí)行,無(wú)法從并行化中獲益。這對(duì) Java™ 平臺(tái)也是如此。特別的,在 J2SE 1.4 以前,Sun Java 平臺(tái)的虛擬機(jī)并不支持并行垃圾回收,這樣,在多處理器系統(tǒng)中,垃圾回收會(huì)對(duì)并行應(yīng)用產(chǎn)生嚴(yán)重影響。

下圖顯示了一個(gè)除了垃圾回收以外均為完美可伸縮的理想系統(tǒng)的性能曲線。紅色曲線是一個(gè)在但處理器系統(tǒng)中會(huì)花費(fèi) 1% 的時(shí)間在垃圾回收上的程序。它在 32 處理器的系統(tǒng)中,將損失 20% 的吞吐量。而一個(gè)花費(fèi) 10% 時(shí)間在垃圾回收上的應(yīng)用(不考慮單處理器系統(tǒng)中額外的垃圾回收時(shí)間)在系統(tǒng)擴(kuò)張到 32 處理器系統(tǒng)中時(shí),會(huì)損失超過 75% 的吞吐量 。

這意味著在小型開發(fā)系統(tǒng)中微不足道的速度問題當(dāng)擴(kuò)張到大規(guī)模系統(tǒng)中就可能成為嚴(yán)重的性能瓶頸。從另一個(gè)角度看,減少這樣的性能瓶頸的小改動(dòng)就可以獲得很大的性能收益。對(duì)足夠大規(guī)模的系統(tǒng),選擇合適的垃圾收集器并進(jìn)行必要調(diào)優(yōu)是絕對(duì)值得的。

對(duì)于大多數(shù)“小”應(yīng)用(在現(xiàn)代處理器上大約需要100MB堆內(nèi)存的應(yīng)用)來說通常是足夠的。其他垃圾收集器會(huì)帶來額外的負(fù)載或復(fù)雜性,這回讓系統(tǒng)的某些行為付出一定的代價(jià)。如果一個(gè)應(yīng)用不需要一個(gè)垃圾收集器的某個(gè)功能。那么就使用串行的垃圾收集器好了。一個(gè)不應(yīng)該使用串行垃圾收集器場(chǎng)景是一個(gè)超多線程的大程序運(yùn)行在一個(gè)大型的、有大量?jī)?nèi)存和兩個(gè)或多個(gè)處理器的系統(tǒng)中。當(dāng)應(yīng)用運(yùn)行在這些服務(wù)器級(jí)的計(jì)算機(jī)上的時(shí)候,并行垃圾收集器會(huì)被缺省選擇(參見下面的功效學(xué) )。

本文以 Solaris™ 操作系統(tǒng)(SPARC(R) 平臺(tái)版本)中的 Java SE 6 作為參考。不過,文中所述的概念和建議適用于所有支持的平臺(tái),包括 Linux, Microsoft Windows 和 Solaris 操作系統(tǒng)(x86 平臺(tái)版本)。此外,文中的命令行參數(shù)也對(duì)所有平臺(tái)有效,雖然它們的缺省值在各個(gè)平臺(tái)可能有所不同。

2. 功效學(xué)(Ergonomics)

“功效學(xué)”是一個(gè) J2SE 5.0 引入的概念。引入功效學(xué)概念是為了通過不設(shè)置或設(shè)置很少的幾個(gè)命令行參數(shù)的情況下提供更好的性能,這些參數(shù)包括:

  • 垃圾收集器,
  • 堆尺寸,
  • 和運(yùn)行時(shí)編譯器

這里的參數(shù)選擇假定應(yīng)用所運(yùn)行的主機(jī)類型和應(yīng)用的類型一致(也就是說,大型應(yīng)用運(yùn)行在大型的機(jī)器上)。這些選項(xiàng)簡(jiǎn)化了垃圾回收的調(diào)優(yōu)。選擇并行垃圾回收器,用戶可以指定應(yīng)用的最大中斷時(shí)間和希望的吞吐量。這和指定堆大小來調(diào)優(yōu)性能是相對(duì)應(yīng)的。最常用的功效學(xué)相關(guān)的內(nèi)容在可以參考 “Ergonomics in the 5.0 Java Virtual Machine” 這篇文章。建議在嘗試本文提到的細(xì)節(jié)配置之前嘗試該文章中介紹的功效學(xué)手段。

本文中的功效學(xué)特性被作為并行垃圾回收器的自適應(yīng)尺寸策略的一部分。這包括指定垃圾回收性能的目標(biāo)和性能調(diào)優(yōu)的一些附加選項(xiàng)。

3.代

J2SE 平臺(tái)的優(yōu)勢(shì)之一是它將內(nèi)存分配、垃圾回收這些繁復(fù)的細(xì)節(jié)屏蔽了起來。然而,一旦垃圾回收成為主要的瓶頸,那么理解一下這些隱藏在背后的細(xì)節(jié)就變得有必要了。垃圾回收器對(duì)應(yīng)用程序?qū)?duì)象的使用方式進(jìn)行判斷,這個(gè)判斷會(huì)反映在可調(diào)優(yōu)參數(shù)中,他們可以被調(diào)整,以提高性能而不犧牲掉抽象性。

當(dāng)一個(gè)對(duì)象不再可能被從其他任何地方訪問到的時(shí)候就會(huì)被認(rèn)為是垃圾了。最直接的垃圾回收算法就是簡(jiǎn)單地迭代所有可找到的對(duì)象。任何沒有被跌帶到的對(duì)象都可以被認(rèn)為是垃圾了。這個(gè)方法的用時(shí)和活著的對(duì)象數(shù)量成正比,這對(duì)于那些維護(hù)著大量活數(shù)據(jù)的程序來說是不可接受的。

從 J2SE 1.2 開始,虛擬機(jī)就引入了各種不同的垃圾回收算法,這些算法都使用分代垃圾收集。盡管原生的垃圾回收會(huì)檢查堆中的所有活著的對(duì)象,分代垃圾收集采用了很多觀測(cè)到的大部分應(yīng)用程序的經(jīng)驗(yàn)特征,用來最小化發(fā)現(xiàn)廢棄的對(duì)象的工作量。最重要的經(jīng)驗(yàn)特征是 weak generational 假設(shè),該假設(shè)認(rèn)為大部分對(duì)象都只存活一少段時(shí)間。

下圖中的藍(lán)色區(qū)域是對(duì)象生存期的典型分布。橫軸是對(duì)象被分配后的生存期??v軸方向計(jì)算的字節(jié)數(shù)是相應(yīng)生存期的對(duì)象的總字節(jié)數(shù)。左側(cè)的尖峰表明,對(duì)象在分配之后不久就被廢棄了。比如,迭代器對(duì)象常常只會(huì)在一個(gè)循環(huán)中被用到。

當(dāng)然,有些對(duì)象確實(shí)活得要長(zhǎng)一些,于是,分布曲線延伸到了右邊。比如,典型情況下,有些對(duì)象在初始化的時(shí)候被創(chuàng)建,并一直存活到進(jìn)程結(jié)束。在這兩種極限情況之間,那些對(duì)象活的時(shí)間也是中等的,在圖中表現(xiàn)初來的就是從開始的峰值泄漏初來的藍(lán)色區(qū)域。有些應(yīng)用可能會(huì)有看起來十分不同的分布曲線,不過絕大多數(shù)的進(jìn)程都是這個(gè)常見的形狀。大部分對(duì)象都會(huì)“英年早逝”這個(gè)事實(shí)讓高效的垃圾收集變得具有可能性了。

為了為這樣的應(yīng)用環(huán)境優(yōu)化,內(nèi)存被按照“代” (generation)進(jìn)行管理,或者說,內(nèi)存池中存放不同年齡的對(duì)象。當(dāng)一個(gè)年齡斷被填滿后,就對(duì)該代的垃圾進(jìn)行回收。在內(nèi)存池中的大部分對(duì)象都是年輕的對(duì)象(年輕的代),而大部分對(duì)象也會(huì)在年輕的時(shí)候就成為垃圾。當(dāng)年輕代被填滿的時(shí)候,會(huì)導(dǎo)致一次“小回收”(譯注:原文minor,似乎“未成年”更貼切一些,不過咱們讀起來會(huì)很別扭),這里只有年輕代的對(duì)象惠北回收,而其他年齡斷的垃圾則不與理會(huì)。該回收算法的成本是,一階情況下,正比于被回收的活的對(duì)象的數(shù)量;年輕代因?yàn)闈M是死對(duì)象,所以回收非常迅速。而在“小回收”中存活下來的對(duì)象于是乎就會(huì)被轉(zhuǎn)移到所謂的年老代(tenured generation)。最終,當(dāng)年老代被填滿而需要回收的時(shí)候,就會(huì)導(dǎo)致一次主回收,這時(shí)整個(gè)堆都會(huì)被回收。主回收通常會(huì)運(yùn)行锝比小回收慢很多,因?yàn)榇罅康膶?duì)象都會(huì)被處理。

如上文記述,對(duì)不同的應(yīng)用,“工效學(xué)”會(huì)動(dòng)態(tài)選擇垃圾收集器來提供較好的性能。串行垃圾收集器用于哪些數(shù)據(jù)量比較小的程序,而且它的缺省參數(shù)也讓大多數(shù)小程序能夠高效工作。而大吞吐量垃圾收集器用于那些有中到大數(shù)據(jù)量的數(shù)據(jù)集。工效學(xué)選擇的堆尺寸參數(shù)和自適應(yīng)尺寸策略用于為服務(wù)器提供更好的性能。這些選擇的大多數(shù)而不是所有的情況下工作得很不錯(cuò)。這就引出了本文的核心宗旨:

如果垃圾收集器成為了瓶頸,你可能不得不調(diào)整整個(gè)堆的大小乃至每個(gè)代的尺寸。檢查垃圾收集器的詳細(xì)輸出,然后檢查垃圾收集器對(duì)你關(guān)注的各個(gè)性能指標(biāo)的影響。

(并行垃圾收集器之外的)缺省的代排布大概就是這樣的。

初始化的時(shí)候,最大的地址空間虛擬地保留住而沒有分配出去,直到真的需要的時(shí)候?yàn)橹埂U麄€(gè)保留的對(duì)象地址空間被分給了年輕的和年老的代。

年輕代包括“伊甸園”和兩個(gè)幸存者空間。大部分對(duì)象最初在伊甸園里被分配出來。一個(gè)幸存者空間在任意時(shí)刻都是空的,作為伊甸園中的活對(duì)象的目的地,另一個(gè)是用于下一次收集。對(duì)象在幸存者空間之間停留到足夠老之后,就會(huì)被復(fù)制到年老代去了。

另一個(gè)和年老代有密切關(guān)系的代是永久的(permanent)代,這里保存著虛擬機(jī)需要的用來描述那些 Java 語(yǔ)言層面沒有等價(jià)物的對(duì)象。比如,那些描述類和方法的的對(duì)象就存放在永久代。

3.1 性能考慮

對(duì)于垃圾回收的性能,主要有兩種量度方法:

  1. 吞吐量。吞吐量是在一段足夠長(zhǎng)的時(shí)間中,沒有花費(fèi)在垃圾回收上的時(shí)間占總時(shí)間的百分比。吞吐量包含了花在空間費(fèi)配上的時(shí)間(不過空間分配速度的調(diào)優(yōu)一般是沒有必要的)。
  2. 延時(shí)。延時(shí)是由于等待垃圾回收而導(dǎo)致的程序沒有響應(yīng)的時(shí)間。

不同的用戶對(duì)垃圾收集有不同的需求。比如,對(duì)于一個(gè)web server而言,吞吐量是合理的量度,因?yàn)槔占瘞淼亩虝r(shí)時(shí)延是可以容忍的,或者說是很容易就被網(wǎng)絡(luò)時(shí)延所掩蓋了。不過,對(duì)于交互的圖形界面程序而言,極短的停頓都會(huì)影響用戶的使用體驗(yàn)。

有些用戶對(duì)其他的因素很敏感。Footprint是一個(gè)進(jìn)程的工作集,由頁(yè)和cache line來量度。對(duì)于內(nèi)存相對(duì)于進(jìn)程數(shù)量很有限的系統(tǒng)而言。Footprint會(huì)影響到程序的可伸縮性。Promptness是對(duì)象死掉和該塊內(nèi)存重新可用之間的時(shí)間間隔的量度,這是分布式系統(tǒng)的一個(gè)重要考慮因素,包括遠(yuǎn)程方法調(diào)用(RMI)。

總的說,一個(gè)特定的代的尺寸選擇是上述這些因素之間的權(quán)衡的結(jié)果。比如,一個(gè)非常大的年輕代的大小可以最大化吞吐律,但會(huì)以Footprint、 Promptness和延時(shí)作為代價(jià)。而年輕代延時(shí)可以通過縮小該代的大小來達(dá)到最小化,但同樣會(huì)損失吞吐量。近似地,調(diào)整一個(gè)代的尺寸不會(huì)影響到其他代的垃圾收集頻率和時(shí)延。

沒有一個(gè)簡(jiǎn)單的方法來設(shè)置代的尺寸。最好的選擇由程序使用內(nèi)存的方式和用戶的需求來決定。這樣,虛擬機(jī)對(duì)垃圾收集器的選擇并不總是最優(yōu)的,而且可以通過后面介紹的命令行參數(shù)來調(diào)整。

3.2 測(cè)量

使用應(yīng)用特定的量度,吞吐量和footprint很容易被測(cè)量。例如,web服務(wù)器的吞吐量可以使用一個(gè)客戶端負(fù)載生成器來測(cè)量,而該服務(wù)器的 footprint 則可以在 Solaris 操作系統(tǒng)中使用 pmap 命令來測(cè)量。另一方面,垃圾收集導(dǎo)致的時(shí)延可以方便地通過監(jiān)測(cè)虛擬機(jī)自己的診斷輸出來估算出來。

命令行參數(shù) -verbos:gc 可以送出每一次垃圾收集時(shí)的堆和垃圾收集信息。比如,這是一個(gè)大型服務(wù)器應(yīng)用的輸出:

  1. [GC 325407K->83000K(776768K), 0.2300771 secs]  
  2. [GC 325816K->83372K(776768K), 0.2454258 secs]  
  3. [Full GC 267628K->83769K(776768K), 1.8479984 secs] 

這里是兩次小回收和之后的一次主回收。箭頭前后的數(shù)字(比如第一行的325407K->83000K)分別指垃圾回收前后的所有活著的對(duì)象占用的空間。在小回收之后,這個(gè)尺寸之中仍然包含一些沒有被回收的垃圾(死掉的對(duì)象)。這些對(duì)象要么存在在年老代中,要么被年老或永久代中的對(duì)象所引用。

后面的括號(hào)中的數(shù)字(比如第一行中的 (776768K))是全部提交的堆大小,也就是虛擬己不向操作系統(tǒng)申請(qǐng)內(nèi)存的情況下,全部 java 對(duì)象可用的存儲(chǔ)空間。注意,這個(gè)數(shù)字不包括幸存者空間中的一個(gè),因?yàn)樾掖嬲呖臻g在一個(gè)給定時(shí)間只有一個(gè)可用,同時(shí)也不包括永久代的空間,這里面是虛擬機(jī)使用的元數(shù)據(jù)。

最后一個(gè)數(shù)字(比如 0.2300771 secs)是垃圾收集所用的時(shí)間;這個(gè)例子里大約是四分之一秒。

第三行中主垃圾回收的格式也是類似的。

-verbos:gc 輸出的格式可能在將來的版本里有所改變。

通過-XX:+PrintGCDetails參數(shù)可以查看更多垃圾回收相關(guān)的信息。下面是串行垃圾收集器使用該參數(shù)打印出來的信息。

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

這個(gè)信息顯示,這次小回收收回了 98% 的 DefNew 年輕代的數(shù)據(jù),64575K->959K(64576K) 并在其上消耗了 0.0457646 secs(大約45毫秒)。

整個(gè)堆的占用率下降了大約51% 196016K->133633K(261184K),而且通過最終的時(shí)間 0.0459067 secs 顯示在垃圾收集中有輕微的開銷(在年輕代之外的時(shí)間)。

選項(xiàng)-XX:+PrintGCTimeStamps會(huì)提供每次回收開始時(shí)間的時(shí)間戳。這對(duì)于查看垃圾回收頻率非常有用。

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]

如上,垃圾回收在程序運(yùn)行后111秒開始。小回收同時(shí)啟動(dòng)。信息中還顯示了主回收中的年老代的垃圾回收信息。年老代的空間使用率下降了大約 10% 18154K->2311K(24576K) ,用時(shí) 0.1290354(大約130毫秒)。

和 -verbose:gc 一樣,-XX:+PrintGCDetails 的輸出格式在將來的版本里也可能會(huì)有所變動(dòng)。

#p#

4. 代的尺寸

很多參數(shù)會(huì)應(yīng)想到代的尺寸。下圖是堆中的提交空間和虛擬空間的差別。虛擬機(jī)初始化的時(shí)候,整個(gè)堆空間都是保留的。保留空間可以通過參數(shù) -Xmx 指定。如果-Xms參數(shù)小于-Xmx參數(shù),那么不是所有的保留空間都會(huì)立刻提交到虛擬機(jī)之中。未提交的空間在途中標(biāo)記為 virtual。堆的不同部分(永久時(shí)間段、年老時(shí)間段和年輕時(shí)間段)可以按需生長(zhǎng)到虛擬空間的限制為止。

一些參數(shù)可以調(diào)整堆的不同部分的比例,比如參數(shù)NewRatio指定年老代對(duì)年輕代的比例。這些參數(shù)將在下面討論。

4.1 全部堆

注意,下面的關(guān)于堆的生長(zhǎng)、收縮和缺省堆大小都不適用于并行垃圾收集器,并行垃圾收集器請(qǐng)參考相關(guān)章節(jié)。不過,用于控制整個(gè)堆大小和代尺寸的參數(shù)對(duì)并行垃圾收集器都是適用的。

因?yàn)槔占前l(fā)生在代被填滿的時(shí)候,所以,吞吐量反比于可用此內(nèi)存數(shù)量??偪捎脙?nèi)存數(shù)是影響垃圾收集性能的最重要因素。

缺省情況下,虛擬己在每次垃圾收集后增加或減少堆尺寸,來盡量保持可用空間對(duì)活的對(duì)象之間的比例在一個(gè)區(qū)間之內(nèi)。這個(gè)目標(biāo)區(qū)間通過參數(shù) -XX:MinHeapFreeRatio=<minimum>和-XX:MaxHeapFreeRatio=<maximum& gt;來設(shè)置,而總的堆大小的界限由-Xms<min>和-Xmx<max>來確定。這些參數(shù)在 32 位 Solaris 系統(tǒng)(SPARC 平臺(tái)版本)中的缺省值如下表所示:

  1. Parameter   
  2. Default Value   
  3. MinHeapFreeRatio  
  4. 40  
  5. MaxHeapFreeRatio  
  6. 70  
  7. -Xms  
  8. 3670k  
  9. -Xmx  
  10. 64m 

64位系統(tǒng)中的堆尺寸的參數(shù)會(huì)大 30% 左右,這個(gè)增長(zhǎng)用來補(bǔ)償64位系統(tǒng)中更大的對(duì)象所帶來的開銷。

通過設(shè)置這些參數(shù),當(dāng)一個(gè)代的可用空間低于 40%,虛擬機(jī)就會(huì)把可用內(nèi)存擴(kuò)展到 40%,直到代的最大尺寸。同樣的,如果可用空間超過 70%,代就會(huì)被縮小,使得只有 70% 可用空間,直到達(dá)到代最小的空間為止。

大型服務(wù)器程序在使用這些缺省設(shè)置時(shí),經(jīng)常遇到兩種問題。其一是慢啟動(dòng)問題,初始的堆尺寸過小,經(jīng)常需要經(jīng)歷多次主回收才能達(dá)到穩(wěn)定值。另一個(gè)更現(xiàn)實(shí)的問題是,對(duì)于大多數(shù)服務(wù)器應(yīng)用來說,這個(gè)缺省的最大堆大小太小了。對(duì)于服務(wù)器程序而言,設(shè)置的一般原則是:

  • 除非遇到了時(shí)延問題,給虛擬機(jī)盡量多的內(nèi)存。缺省尺寸(64MB)通常都太小了。
  • 把-Xms 和 -Xmx 設(shè)置成相同的值,把最重要的尺寸決定從虛擬機(jī)收回來,從而增強(qiáng)可預(yù)見性。
  • 一般地,隨著處理器數(shù)量的增加而增加內(nèi)存,因?yàn)閮?nèi)存分配可以被并行化。

作為參考,有一個(gè)單獨(dú)的頁(yè)面會(huì)介紹各個(gè)命令行參數(shù) 。

4.2 年輕代

影響位居次席的是用于年輕代的堆比例。年輕代越大,小回收的次數(shù)也就越少。不過,在一定的堆大小的情況下,年輕代越大,年老代也就越小,這就增加了主回收的頻率。最佳選擇依賴于應(yīng)用中分配的對(duì)象的生存期分布。

缺省的,年輕代的尺寸由 NewRatio 控制。比如,設(shè)置-XX:NewRatio=3意味著年輕代和年老代的比例是1:3。換句話說, eden 和幸存者空間的總和是整個(gè)堆大小的四分之一。

參數(shù) NewSize 和 MaxNewSize 約束了年輕代的上下界限??梢园堰@兩個(gè)參數(shù)設(shè)成相同的值來固定年輕代的大小,設(shè)置 -Xms 和 -Xmx 一樣來設(shè)置堆大小為固定值。這樣可以比使用NewRatio更細(xì)粒度地調(diào)整年輕代的大小。

4.2.1 幸存者空間

如果需要,SurvivorRatio 可以用來調(diào)整幸存者空間的大小,不過這對(duì)于性能一般影響不大。比如,-XX:SurvivorRatio=6 會(huì)設(shè)置幸存者空間和eden的比例是 1:6。換句話說,每個(gè)幸存者空間將是 eden 的六分之一,是整個(gè)年輕代空間的八分之一(不是七分之一,因?yàn)橐还灿袃蓚€(gè)幸存者空間)。

如果幸存者空間過小的話,拷貝收集到的幸存者將會(huì)直接溢出到年老代的空間中去。如果幸存者空間太大的話,他們也就是空著浪費(fèi)掉。每次垃圾收集中,虛擬機(jī)會(huì)選擇一個(gè)對(duì)象在成為年老的之前被復(fù)制的次數(shù)門限。這個(gè)門限的設(shè)置會(huì)保證幸存者空間是半滿的。命令行參數(shù) -XX:+PrintTenuringDistribution 可以顯示這個(gè)門限和年輕代中對(duì)象的年齡。這對(duì)于觀測(cè)應(yīng)用中對(duì)象的生存期分布也是有用的。

下面是 SPARC 上的 32 位 Solaris 的各個(gè)參數(shù)的缺省值,在其他平臺(tái)上可能有所差異。

  1. Default Value   
  2. Parameter   
  3. Client JVM   
  4. Server JVM   
  5. NewRatio  
  6. 8  
  7. 2  
  8. NewSize  
  9. 2228K  
  10. 2228K  
  11. MaxNewSize  
  12. not limited  
  13. not limited  
  14. SurvivorRatio  
  15. 32  
  16. 32 

年輕代的最大尺寸通過最大堆尺寸和 NewRatio 計(jì)算而得。所謂的“無(wú)限制”的缺省值是說這個(gè)計(jì)算的值不會(huì)受到 MaxNewSize 的約束,除非命令行中指定了這個(gè)值。

服務(wù)應(yīng)用的設(shè)置準(zhǔn)則是:

  • 首先確定可以提供給虛擬己的最大堆尺寸。然后根據(jù)性能需求來確定年輕代的尺寸,來找到最佳設(shè)置。
    • 注意:最大堆尺寸一定要小于系統(tǒng)中的內(nèi)存數(shù)量,以防止過多的缺頁(yè)錯(cuò)誤和換頁(yè)。
  • 如果總的堆尺寸是確定的,增加年輕代的尺寸就會(huì)減少年老代的尺寸。一定要保證年老代的尺寸,使之可以容納所有在應(yīng)用全程都要用到的活對(duì)象,并留有一定裕量(10-20%或更多)。
  • 依照上述年老代的約束:
    • 給年輕代分配足夠的內(nèi)存。
    • 如果有多個(gè)處理器,那么分配更多的內(nèi)存給年輕代,因?yàn)閮?nèi)存分配可以并行化。

5. 可用的垃圾收集器

到目前為止,我們討論的還都是串行垃圾收集器。不過 Java HotSpot 虛擬機(jī)一共支持了三種不同的收集器,每種提供不同的性能特性。

  1. 串行垃圾收集器使用單線程進(jìn)行所有垃圾收集工作,因?yàn)闆]有線程間通信的開銷,串行垃圾收集器相當(dāng)高效。串行垃圾收集器最適合于單處理器系統(tǒng),因?yàn)樗粫?huì)從多處理器硬件中獲益,盡管在小數(shù)據(jù)量的應(yīng)用中(不大于100MB的),它對(duì)于多處理器系統(tǒng)也是游泳的。串行垃圾收集器在一定的硬件和操作系統(tǒng)的配置時(shí)會(huì)缺省使用,也可以顯式地用 -XX:+UseSerialGC 參數(shù)來指定。
  2. 并行垃圾收集器(或吞吐垃圾收集器)并行進(jìn)行小垃圾收集,這會(huì)顯著減少垃圾收集的的開銷。它適用于中等或大尺寸數(shù)據(jù)的運(yùn)行在多處理器或多線程硬件上的應(yīng)用。并行垃圾收集器也會(huì)在一定的硬件和操作系統(tǒng)配置下被缺省使用,同時(shí),也可以使用 -XX:+UseParallelGC 參數(shù)來指定。
    • 更新:“并行壓縮”是 J2SE 5.0 update 6 以上版本的新特性,并在 Java SE 6 之中得到加強(qiáng),該特性允許主回收也并行收集。如果不使用并行壓縮,主回收仍然會(huì)單線程運(yùn)行,這會(huì)嚴(yán)重限制系統(tǒng)的可伸縮性。并行壓縮可以使用命令行參數(shù) -XX:+UseParallelOldGC 來打開。
  3. 并發(fā)垃圾收集器并發(fā)地進(jìn)行大部分垃圾收集工作(也就是在應(yīng)用運(yùn)行當(dāng)中進(jìn)行)來盡可能煎炒垃圾收集帶來的應(yīng)用停頓。它是為哪些擁有中到大量數(shù)據(jù)的、對(duì)響應(yīng)時(shí)間要求高于吞吐量要求的應(yīng)用,因?yàn)樽钚』瘯r(shí)延的技術(shù)會(huì)讓吞吐能力付出代價(jià)。并發(fā)垃圾收集器通過 -XX:+UseConcMarkSweepGC 參數(shù)來啟用。
5.1 選擇垃圾收集器

除非你的應(yīng)用有非常嚴(yán)酷的時(shí)延要求,那么就運(yùn)行你的應(yīng)用,并讓系統(tǒng)自己選擇垃圾收集器好了。如果有必要的話,就調(diào)整堆的大小來增進(jìn)性能。如果性能仍然無(wú)法達(dá)到你的目標(biāo),那就按照如下設(shè)置來選擇一個(gè)垃圾收集器。

  1. 如果應(yīng)用的數(shù)據(jù)很少(大約不超過100MB),那么
    • 使用-XX:+UseSerialGC選擇串行垃圾收集器。
  2. 如果應(yīng)用運(yùn)行在單處理器系統(tǒng)中,并且沒有什么時(shí)延要求,那么
    • 讓虛擬機(jī)選擇垃圾收集器,或者
    • 使用-XX:+UseSerialGC選擇串行垃圾收集器。
  3. 如果(a)程序峰值性能是第一位的,并且(b)沒有時(shí)延要求,或時(shí)延要求是一兩秒或更長(zhǎng),那么
    • 讓虛擬機(jī)選擇垃圾收集器,或者
    • 使用-XX:+UseParallelGC選擇并行垃圾收集器,乃至(可選)通過 -XX:+UseParallelOldGC啟用并行壓縮。
  4. 如果響應(yīng)時(shí)間比總體吞吐量更為重要,并且垃圾收集時(shí)延需要控制在1秒以內(nèi),那么
    • select the concurrent collector with -XX:+UseConcMarkSweepGC. If only one or two processors are available, consider using incremental mode, described below.
    • 通過 -XX:+UseConcMarkSweepGC 參數(shù)啟用并發(fā)垃圾收集器。進(jìn)當(dāng)你有一個(gè)或兩個(gè)處理器可用的時(shí)候,考慮使用下文將要介紹的“增量模式”。

這些指導(dǎo)意見僅僅是選擇垃圾收集器的起點(diǎn),因?yàn)樾阅芤蕾囉诙训某叽纭?yīng)用中活數(shù)據(jù)的數(shù)量,以及處理器的數(shù)量和速度。時(shí)延參數(shù)對(duì)這些因素尤為敏感,所以,所謂的1秒門限值只是個(gè)大致數(shù)值:在很多硬件和數(shù)據(jù)量的組合情況下,并行垃圾收集器可能會(huì)導(dǎo)致停頓時(shí)間超過1秒;同樣,在某些組合下,并發(fā)垃圾收集器也不能保證停頓小于1秒。

如果推薦的垃圾收集器沒有達(dá)到期望的性能,首先應(yīng)該嘗試堆和代的尺寸,以期達(dá)到目標(biāo)。如果仍然不成功的話,嘗試更換一個(gè)垃圾收集器:使用并發(fā)垃圾收集器來減少停頓時(shí)間,使用并行垃圾收集器來增加多處理器系統(tǒng)中的吞吐量。

#p#

6. 并行垃圾收集器

并行垃圾收集器(也被稱為吞吐量收集器)和串行收集器類似,也是一種分代垃圾收集器;其最大的不同在于它使用了多線程來加快垃圾收集的過程。并行垃圾收集器可以通過參數(shù) -XX:+UseParallelGC 指定。缺省的,只有小回收會(huì)并行運(yùn)行,主回收仍然單線程運(yùn)行。不過,通過參數(shù)-XX:+UseParallelOldGC啟動(dòng)并行壓縮可以讓主回收和小回收都并行運(yùn)行,從而進(jìn)一步減少垃圾收集開銷。

在一個(gè)有N個(gè)處理器的計(jì)算機(jī)上,并行垃圾收集器使用N個(gè)垃圾收集器線程。不過,這個(gè)數(shù)量可以在命令行參數(shù)里指定(參見下文)。在一臺(tái)單處理器的計(jì)算機(jī)上,由于線程開銷(比如同步),并行垃圾收集器的性能應(yīng)該不如串行垃圾收集器。然而,當(dāng)應(yīng)用程序有中等或大尺寸的堆的時(shí)候,它在一個(gè)雙處理器的機(jī)器上就會(huì)略優(yōu)于串行垃圾收集器,而如果有多于兩個(gè)處理器的話,它就能遠(yuǎn)勝于串行垃圾收集器。

垃圾收集器線程數(shù)的多少可以用-XX:ParallelGCThreads=<N>參數(shù)來控制。如果要使用命令行參數(shù)顯式調(diào)整了堆的尺寸,使用并行垃圾收集器的情況下需要的堆的尺寸和使用串行垃圾收集器情況下的堆的尺寸是一階相等的。使用并行垃圾收集器僅僅是讓小回收造成的停頓更短一些。因?yàn)橛卸鄠€(gè)垃圾收集器線程參與小回收的過程,有極少的可能性可能會(huì)在將年輕代移動(dòng)到年老代的過程中造成一些碎片。每個(gè)垃圾收集線程都有一塊專屬的年老代的空間,用于年輕代向年老代的移動(dòng),將年老代的可用空間劃分為“移動(dòng)緩沖”(promotion buffer)的過程可能會(huì)造成一定的碎片效應(yīng)。減少垃圾收集器線程的數(shù)量可以減少碎片、增加年老代的空間。

6.1 代

正如上面提到的,并行垃圾收集器的代的排布方式和串行垃圾收集器略有不同。其分布如下圖所示。

6.2 功效學(xué)

自 J2SE 5.0 以來,并行垃圾收集器成為了server級(jí)機(jī)器的缺省垃圾收集器,詳細(xì)資料可以參考“Garbage Collector Ergonomics”。此外,并行垃圾收集器使用一種自動(dòng)調(diào)整機(jī)制來指定期望的行為而不是指定代的大小和其他底層調(diào)整細(xì)節(jié)。這些行為包括:

  • 最大垃圾收集停頓時(shí)間
  • 吞吐量
  • Footprint (也就是堆尺寸)

最大停頓時(shí)間的目標(biāo)由參數(shù)-XX:MaxGCPauseMillis=<N>來指定。這個(gè)參數(shù)被解釋為指定停頓時(shí)間不得大于< N>毫秒;缺省情況下沒有最大停頓時(shí)間目標(biāo)。如果指定了一個(gè)停頓時(shí)間目標(biāo),堆尺寸和其他垃圾回收相關(guān)參數(shù)就會(huì)被相應(yīng)調(diào)整,以便保持垃圾回收時(shí)間小于指定的值。注意,這些調(diào)整可能會(huì)導(dǎo)致總體吞吐量的降低,而且,在某些情況下,要求的停頓時(shí)間目標(biāo)可能無(wú)法達(dá)到。

吞吐量目標(biāo)測(cè)量垃圾回收時(shí)間和非垃圾回收時(shí)間(也就是應(yīng)用時(shí)間)的比例。這個(gè)目標(biāo)時(shí)間可以用命令行參數(shù)-XX:GCTimeRatio=< N>來指定,這樣,垃圾回收時(shí)間和應(yīng)用時(shí)間的比例將是1 / (1 + <N>)。例如,-XX:GCTimeRatio=19設(shè)置1/20活5%的時(shí)間用于垃圾回收。缺省值是99,目標(biāo)是1%的時(shí)間用于垃圾回收。

最大堆footprint使用已經(jīng)存在的 -Xmx<N> 參數(shù)。此外,如果沒有其他的優(yōu)化目標(biāo)的話,垃圾收集器有一個(gè)隱式的最小化堆尺寸的目標(biāo)。

6.2.1 目標(biāo)的優(yōu)先級(jí)

目標(biāo)的優(yōu)先級(jí)順序如下:

  1. 最大停頓時(shí)間目標(biāo)
  2. 吞吐量目標(biāo)
  3. 最小堆尺寸目標(biāo)

最大停頓時(shí)間目標(biāo)會(huì)被首先滿足。僅當(dāng)最大停頓目標(biāo)被滿足的情況下,才會(huì)去滿足吞吐量目標(biāo)。類似的,僅當(dāng)前兩個(gè)目標(biāo)都會(huì)滿足的情況下,才會(huì)考慮去滿足footprint目標(biāo)。

6.2.2 時(shí)間段尺寸調(diào)整

每次垃圾收集結(jié)束的時(shí)候,垃圾收集器都會(huì)更新其保存的平均停頓時(shí)間之類的統(tǒng)計(jì)參量。同時(shí)它會(huì)檢查各個(gè)目標(biāo)是否被滿足了,是否有調(diào)整代尺寸的需要。這之中的意外情況就是顯式的垃圾收集(比如調(diào)用 System.gc())會(huì)在統(tǒng)計(jì)和調(diào)整判斷中被忽略掉。

增加和縮小一個(gè)代的大小是通過增加活縮小一個(gè)固定的百分比來達(dá)到的,這樣一個(gè)代要分步來達(dá)到需要的尺寸。增加活所見是以不同的比率來進(jìn)行的。缺省情況下,一次增加 20% 活減少 5%。年輕代和年老代增量的比例分別通過命令行參數(shù) -XX:YoungGenerationSizeIncrement=<Y>和 -XX:TenuredGenerationSizeIncrement=<T>來設(shè)定。而縮小比例的要通過 -XX:AdaptiveSizeDecrementScaleFactor=<D>參數(shù)來設(shè)定。如果增量是X%,那么每次減小量就是 (X/D)%。

如果垃圾收集器決定在啟動(dòng)的時(shí)候增加一個(gè)代的大小,會(huì)有一個(gè)額外的百分比的增量。這個(gè)附加的增量隨著收集的次數(shù)而減少,不會(huì)長(zhǎng)期影響。這個(gè)額外增量意在提高啟動(dòng)速度。縮小代的尺寸是沒有這個(gè)額外的增量。

如果最大停頓時(shí)間目標(biāo)沒有達(dá)到,會(huì)有且僅有一個(gè)代的大小被縮小。如果兩個(gè)代都在目標(biāo)之上,停頓時(shí)間較大的那個(gè)代會(huì)首先被縮小。

如果總體吞吐量目標(biāo)沒有達(dá)到,那么兩個(gè)代的大小都會(huì)增加。每個(gè)都按照各自對(duì)垃圾回收時(shí)間的貢獻(xiàn)比例分別增加。比如,如果年輕代的垃圾回收時(shí)間占去了25%的總垃圾回收時(shí)間,并且年輕代的全部增量應(yīng)該是20%,那么這時(shí)它的增量就是5%。

6.2.3 缺省堆尺寸

如果沒有在命令行中進(jìn)行設(shè)置,初始和最大堆尺寸會(huì)通過計(jì)算機(jī)內(nèi)存計(jì)算而得。如下表所示,對(duì)大小占用的內(nèi)存的比例是由參數(shù) DefaultInitialRAMFraction和DefaultMaxRAMFraction來控制的。(表中的 memory 代表計(jì)算機(jī)的系統(tǒng)內(nèi)存數(shù)量。)

  1. Formula   
  2. Default   
  3. initial heap size  
  4. memory / DefaultInitialRAMFraction  
  5. memory / 64  
  6. maximum heap size  
  7. MIN(memory / DefaultMaxRAMFraction, 1GB)  
  8. MIN(memory / 4, 1GB) 

注意,缺省的最大堆尺寸不會(huì)超過1GB,不論系統(tǒng)中到底有多少內(nèi)存。

6.3 過多的GC時(shí)間和OutOfMemory錯(cuò)誤

當(dāng)有過多的時(shí)間花費(fèi)在垃圾收集上的時(shí)候,并行垃圾收集器會(huì)跑出 OutOfMemoryError 錯(cuò)誤:如果超過 98% 的時(shí)間花費(fèi)在垃圾收集上并且只有 2% 的堆被釋放的話,就會(huì)拋出一個(gè) OutOfMemory。這個(gè)功能是用來防止堆太小導(dǎo)致程序長(zhǎng)時(shí)間無(wú)法正常工作而設(shè)計(jì)的。如果必要,這個(gè)功能可以使用命令行參數(shù) -XX:-UseGCOverheadLimit來關(guān)閉。

6.4 測(cè)量

并行垃圾收集器的垃圾收集器詳細(xì)輸出和串行垃圾收集器是一樣的。

7. 并發(fā)垃圾收集器

并發(fā)垃圾收集器適用于那些需要更短的垃圾收集停頓,并能為此付出程序運(yùn)行期處理器資源的應(yīng)用。典型情況下,那些擁有較多長(zhǎng)期存在的對(duì)象(年老代比較大),并且運(yùn)行在擁有兩個(gè)活更多處理器的應(yīng)用可能會(huì)因此獲益。不過,在任何要求很低停頓時(shí)間的應(yīng)用都應(yīng)該考慮這個(gè)垃圾收集器;比如,擁有較小年老代的交互程序在但處理器上使用并發(fā)垃圾收集器就可以收到明顯的好處,特別是使用增量模式的時(shí)候。并發(fā)垃圾收集器可以通過命令行參數(shù) -XX:+UseConcMarkSweepGC來啟動(dòng)。

和其他垃圾收集器類似,并發(fā)垃圾收集器也是分代的;所以也有小回收和主回收。并發(fā)垃圾收集器通過使用獨(dú)立的垃圾收集線程于應(yīng)用本身的線程并發(fā)執(zhí)行跟蹤所有可及的對(duì)象,以期降低主回收導(dǎo)致的停頓。在每個(gè)主回收周其中,并發(fā)垃圾收集器會(huì)在垃圾收集的開始讓所有應(yīng)用線程暫停一下,并在回收中段再暫停一次。第二次暫停相對(duì)而言會(huì)更長(zhǎng)一些,在此期間會(huì)有多個(gè)線程來進(jìn)行收集工作。剩下的收集工作包括大部分的活對(duì)象跟蹤和清除不可及的對(duì)象的工作都由一個(gè)或多個(gè)和應(yīng)用并發(fā)的垃圾收集器線程來進(jìn)行。小回收會(huì)在進(jìn)行的主回收周其中穿插進(jìn)行,其模式和并行垃圾收集器十分類似(特別需要說明的就是,在小回收期間,應(yīng)用線程是會(huì)有停頓的)。

并發(fā)垃圾收集器的基本算法在技術(shù)報(bào)告 A Generational Mostly-concurrent Garbage Collector里有介紹。主義,實(shí)際的實(shí)現(xiàn)細(xì)節(jié)在不同版本里手有細(xì)微的變化的,因?yàn)槔占饕苍谝恢边M(jìn)步。

7.1 并發(fā)性的開銷

并發(fā)垃圾收集器的短主回收停頓時(shí)間是以處理器資源作為代價(jià)的(這些資源如果不用在收集器上肯定就要用在應(yīng)用上了)。最明顯的開銷就是并發(fā)地使用了一個(gè)或多個(gè)處理器資源。在N處理器系統(tǒng)中,垃圾收集的并發(fā)部分會(huì)使用K/N的可用處理器,其中 1<=K<=ceiling{N/4}。(注意,K值的上限將來可能會(huì)有變化。)并發(fā)垃圾收集器不僅在并發(fā)階段使用處理器,還引入了其他的開銷。所以,盡管并發(fā)垃圾收集器顯著減少了程序的停頓,但和其他垃圾收集器相比,應(yīng)用的總體吞吐量會(huì)受到輕微的影響。

在擁有多個(gè)處理器的計(jì)算機(jī)上,在并發(fā)垃圾收集器運(yùn)行的時(shí)候,應(yīng)用程序仍然能使用到CPU,所以,并發(fā)垃圾收集器并沒有讓程序停頓。這通常意味著更短的停頓,談也意味著更少的應(yīng)用可用的處理器資源,并且讓它運(yùn)行得相對(duì)比較慢,特別是當(dāng)應(yīng)用可以完全的利用多個(gè)CPU核心的時(shí)候更是如此。隨著N的上升,垃圾收集器導(dǎo)致的損失會(huì)相對(duì)變小,而從并發(fā)垃圾收集的獲益則相對(duì)提高。下一節(jié)“并發(fā)模式失敗”會(huì)討論這種規(guī)模擴(kuò)張的潛在局限。

因?yàn)樵诓l(fā)階段至少有一個(gè)處理器用于了垃圾收集,所以在單處理器(單核)系統(tǒng)中,并發(fā)垃圾收集器一般不會(huì)帶來什么好處。不過,并發(fā)垃圾收集有一個(gè)分離模式可以在單處理器或雙處理器系統(tǒng)中顯著減少停頓時(shí)間;后面的增量模式中將會(huì)進(jìn)一步介紹其細(xì)節(jié)。

7.2 并發(fā)模式失敗

并發(fā)垃圾收集器使用一個(gè)或多個(gè)垃圾收集線程在應(yīng)用線程執(zhí)行的同時(shí)運(yùn)行,從而在年老代和永久代變滿之前就完成垃圾收集。如前文所述,在一般的操作中,并發(fā)垃圾收集器的大部分跟蹤與清理工作是在程序運(yùn)行的同時(shí)進(jìn)行的,所以,程序線程只有極少的停頓。但是,如果并發(fā)垃圾收集器在年老代變滿的時(shí)候仍沒有完成垃圾清除工作,或是年老代中的可用空間無(wú)法滿足一次分配操作的需要的時(shí)候,應(yīng)用就不得不被暫停下來以等待應(yīng)用線程結(jié)束了。這種無(wú)法并發(fā)地完成垃圾收集的情況被稱為“并發(fā)模式失敗”,這就需要對(duì)并發(fā)垃圾收集器的參數(shù)進(jìn)行調(diào)整了。

7.3 過多的GC時(shí)間和OutOfMemory錯(cuò)誤

并發(fā)垃圾收集器會(huì)在垃圾收集消耗時(shí)間過多的時(shí)候拋出 OutOfMemoryError 錯(cuò)誤:如果多于 98% 的時(shí)間被花費(fèi)在了垃圾手機(jī)上,并且僅有少于 2% 的堆被回收的話,就會(huì)拋出 OutOfMemoryError。這個(gè)功能是用來防止堆太小導(dǎo)致程序長(zhǎng)時(shí)間無(wú)法正常工作而設(shè)計(jì)的。如果必要,這個(gè)功能可以使用命令行參數(shù) -XX:-UseGCOverheadLimit來關(guān)閉。

這個(gè)策略和并行垃圾收集器是基本一致的,惟一的區(qū)別就是并發(fā)的垃圾收集時(shí)間并未計(jì)算在內(nèi)。也就是說,只有哪些程序停頓下來進(jìn)行垃圾收集的時(shí)間才被計(jì)算在內(nèi)了。這些垃圾收集常常是由于并發(fā)模式失敗或是顯式垃圾收集請(qǐng)求(如調(diào)用 System.gc())導(dǎo)致的。

7.4 浮動(dòng)垃圾

并發(fā)垃圾收集器與 HotSpot 中的其他垃圾收集器一樣,是一種識(shí)別至少所有在堆中可以被訪問到的對(duì)象的跟蹤收集器。按照Jones and Lins的說法,是一種增量更新(Incremental Update)垃圾收集器。因?yàn)閼?yīng)用現(xiàn)成和垃圾收集器線程在主回收過程中并發(fā)執(zhí)行,那么那些垃圾收集器跟蹤的對(duì)象就可能在垃圾收集完成之后變成垃圾這些無(wú)法訪問卻還沒有被回收的對(duì)象被稱為浮動(dòng)垃圾(floating garbage)。浮動(dòng)垃圾的數(shù)量取決于垃圾收集周期的長(zhǎng)度和程序中引用更新的頻率,也被稱為轉(zhuǎn)化率(mutation)。而且,另一個(gè)原因是年輕代和年老代的收集是獨(dú)立的,彼此都是對(duì)方的根。一個(gè)粗略的配置規(guī)則是為年老代的浮動(dòng)垃圾多預(yù)留出20%的空間來。一個(gè)垃圾回收周期中的堆中的浮動(dòng)垃圾會(huì)在下一個(gè)垃圾回收周期中被回收。

7.5 時(shí)延(停頓)

并發(fā)垃圾收集器在一個(gè)并發(fā)回收周期中會(huì)兩次暫停應(yīng)用。第一次會(huì)從根從根(比如從對(duì)象線程棧和寄存器、靜態(tài)對(duì)象等的引用)和堆的其他部分(如年輕代)開始標(biāo)記所有直接可達(dá)的活的對(duì)象。第一次停頓被稱為“初始標(biāo)記停頓”(initial mark pause)。第二次停頓發(fā)生在并發(fā)跟蹤階段末尾,用來發(fā)現(xiàn)由于在垃圾收集線程跟蹤完一個(gè)對(duì)象之后又被應(yīng)用線程更新了其引用而沒有被并發(fā)跟蹤到的對(duì)象。這次停頓被稱為“重標(biāo)記停頓”(remark pause)。

7.6 并發(fā)階段

可達(dá)對(duì)象的并發(fā)跟蹤圖發(fā)生在初始標(biāo)記停頓和重標(biāo)記停頓之間。在并發(fā)跟蹤階段中,一個(gè)或多個(gè)并發(fā)垃圾收集器線程會(huì)使用那些本來可能會(huì)被應(yīng)用使用的處理器資源,所以盡管不會(huì)停頓,計(jì)算密集型應(yīng)用可能會(huì)在此階段和其他并發(fā)階段受到相當(dāng)?shù)耐掏铝繐p失。在重標(biāo)記停頓之后,還有一個(gè)并發(fā)清理階段,會(huì)收集所有標(biāo)記為不可達(dá)的對(duì)象。一旦手機(jī)周期結(jié)束了,并發(fā)收集器就會(huì)進(jìn)入等待階段,這時(shí)就基本不會(huì)消耗任何計(jì)算資源了,直到下一個(gè)主回收周期開始為止。

7.7 開始并發(fā)收集周期

在串行收集器中,每當(dāng)年老代滿了的時(shí)候都會(huì)引發(fā)一次主回收,所有應(yīng)用現(xiàn)成都會(huì)在主回收期間暫停運(yùn)行。并發(fā)垃圾收集器與之不同,它需要在足夠早的時(shí)間開始垃圾收集,以便能在年老代變滿之前完成垃圾收集;否則的話就會(huì)因?yàn)椴l(fā)模式失敗而導(dǎo)致較長(zhǎng)的時(shí)延。有很多種條件可以觸發(fā)并發(fā)垃圾收集器啟動(dòng)。

基于最近的歷史記錄,并發(fā)垃圾收集器維護(hù)了一個(gè)年老代變滿的預(yù)期剩余時(shí)間和一個(gè)垃圾收集周期的預(yù)期時(shí)間?;谶@些動(dòng)態(tài)估計(jì),并發(fā)垃圾收集周期會(huì)以讓垃圾收集周期在年老代變滿之前完成為目標(biāo)開始并發(fā)垃圾收集周期。因?yàn)椴l(fā)模式失敗的代價(jià)非常慘重,這些估值都流出了安全裕量。

并發(fā)垃圾收集在年老代的已用百分比超出了一個(gè)初始占有率值(initiating occupancy)的時(shí)候也會(huì)啟動(dòng)。這個(gè)初始占有率閾值的缺省值大約是 92%,不過這個(gè)值可能在不同版本中略有不同。它也可以通過命令行參數(shù)-XX:CMSInitiatingOccupancyFraction=< N> 來手工設(shè)置,其中N是一個(gè)0-100的整數(shù),代表年老代的占用百分比。

7.8 調(diào)度中斷

年輕代和年老代的垃圾收集的停頓發(fā)生彼此間是獨(dú)立的。他們不會(huì)重合,但可能會(huì)連續(xù)發(fā)生,這樣也就讓一個(gè)垃圾收集的停頓連上下一個(gè)垃圾收集的停頓了,從外界來看就是一個(gè)長(zhǎng)停頓了。為了避免這種情況,并發(fā)垃圾收集器會(huì)調(diào)度重標(biāo)記停頓的時(shí)間,使之發(fā)生在前后兩個(gè)年輕代停頓之間。這個(gè)調(diào)度目前還不應(yīng)用于初始標(biāo)記停頓,因?yàn)樗ǔ?huì)比重標(biāo)記停頓短很多。

7.9 增量模式

并發(fā)垃圾收集器可以在這樣一種模式下工作:并發(fā)階段以增量的方式進(jìn)行?;貞浺幌拢诓l(fā)階段,垃圾回收線程會(huì)使用一個(gè)或多個(gè)處理器。所謂增量模式是指減少長(zhǎng)并發(fā)階段的影響,周期性中斷并發(fā)階段,將處理器資源還給應(yīng)用程序。這種模式又稱為“i-cms”,將垃圾收集器的并發(fā)工作劃分到小塊時(shí)間,在年輕代垃圾收集之間進(jìn)行。這個(gè)功能對(duì)于那些工作在沒那么多處理器的機(jī)器上(1或2個(gè)處理器的)需要并發(fā)垃圾收集器的低時(shí)延應(yīng)用非常有用。

并發(fā)垃圾收集周期通常包括如下幾步:

  • 停止所有的應(yīng)用線程,標(biāo)記從根開始可達(dá)的對(duì)象集,然后繼續(xù)所有的應(yīng)用線程
  • 在應(yīng)用線程運(yùn)行的同時(shí),使用一個(gè)或更多的處理器,并發(fā)跟蹤可達(dá)的對(duì)象圖
  • 使用一個(gè)處理器,并發(fā)跟蹤對(duì)象圖中在上一步開始之后的各個(gè)改動(dòng)的部分
  • 停止所有的應(yīng)用線程,重新跟蹤根和對(duì)象圖中自從上次檢查開始發(fā)生了變化的部分,然后繼續(xù)運(yùn)行線程
  • 使用一個(gè)處理器,并發(fā)地把不可達(dá)對(duì)象清理到用于分配空間的 free list 上面去。
  • 使用一個(gè)處理器并發(fā)地調(diào)整堆的大小,準(zhǔn)備下一個(gè)回收周期所需的數(shù)據(jù)結(jié)構(gòu)

正常情況下,并發(fā)垃圾收集器在并發(fā)跟蹤階段使用一個(gè)或多個(gè)處理器,不會(huì)讓出它們。類似的,在清理階段也會(huì)始終獨(dú)占地使用一個(gè)處理器。這對(duì)于對(duì)于一個(gè)程序的響應(yīng)時(shí)間可能是個(gè)不小的影響,特別是系統(tǒng)中只有一兩個(gè)CPU的時(shí)候。增量模式通過將并發(fā)階段分解為一系列的突發(fā)行為來降低這一影響,這些突發(fā)行為會(huì)散布在小回收之間。

i-cms 使用占空比來控制并發(fā)收集器自發(fā)的放棄處理器之前的工作量。占空比是年輕代收集之間的允許并發(fā)垃圾收集器運(yùn)行時(shí)間的百分比。i-cms 可以根據(jù)應(yīng)用的行為自動(dòng)計(jì)算占空比(這也是推薦的方法,稱為自動(dòng)步長(zhǎng)(auto pacing)),當(dāng)然,也可以通過命令行指定一個(gè)固定的值。

7.9.1 命令行參數(shù)

下面是控制 i-cms的命令行參數(shù)(參考下文的初始設(shè)置建議):

參數(shù)

描述

缺省值

J2SE 5.0 及以前

Java SE 6 及以后

-XX:+CMSIncrementalMode

啟動(dòng)增量模式。注意,并發(fā)垃圾收集器必須也被選擇(-XX:+UseConcMarkSweepGC) ,否則此參數(shù)無(wú)效。

disabled

disabled

-XX:+CMSIncrementalPacing

打開自動(dòng)步長(zhǎng),這樣,增量模式占空比將根據(jù)JVM統(tǒng)計(jì)到的信息自動(dòng)調(diào)整。

disabled

enabled

-XX:CMSIncrementalDutyCycle=<N>

兩次小回收之間的允許并發(fā)收集器運(yùn)行的時(shí)間的百分比(0-100)。如果打開自動(dòng)步長(zhǎng),那么這個(gè)值就是初始值。

50

10

-XX:CMSIncrementalDutyCycleMin=<N>

自動(dòng)步長(zhǎng)打開后,占空比值的下限 (0-100)。

10

0

-XX:CMSIncrementalSafetyFactor=<N>

計(jì)算占空比值時(shí)使用的一個(gè)裕量(0-100)

10

10

-XX:CMSIncrementalOffset=<N>

在小回收之間,增量模式中占空比開始的時(shí)間,或說是向右的平移量(0-100)

0

0

-XX:CMSExpAvgFactor=<N>

當(dāng)進(jìn)行并發(fā)回收統(tǒng)計(jì),計(jì)算指數(shù)平均值時(shí),當(dāng)前采樣所用的權(quán)值(0-100)

25

25

7.9.2 建議參數(shù)

要在 Java SE 6 里使用 i-cms,需要使用如下命令行參數(shù)

  1. -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \  
  2. -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 

前兩個(gè)參數(shù)分別啟動(dòng)并發(fā)垃圾收集器和 i-cms。后兩個(gè)參數(shù)不是必須的,它們只是要求垃圾收集器將診斷信息打印到標(biāo)準(zhǔn)輸出,這樣,垃圾收集器的行為就可以被看到并用于以后分析了。

注意,對(duì)于 J2SE 5.0 和之前的版本,我們建議 i-cms 使用如下的初始命令行參數(shù):

  1. -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \  
  2. -XX:+PrintGCDetails -XX:+PrintGCTimeStamps \  
  3. -XX:+CMSIncrementalPacing   
  4. -XX:CMSIncrementalDutyCycleMin=0 
  5. -XX:CMSIncrementalDutyCycle=10 

這樣,就是用了和 Java SE 6 一致的參數(shù)了,多出的三個(gè)參數(shù)用于自動(dòng)調(diào)整占空比。這些多余的參數(shù)值完全是使用的 Java SE 6 的缺省值。

#p#

7.9.3 基本問題處理

i-cms 的自動(dòng)占空比計(jì)算模式使用了程序運(yùn)行時(shí)收集到的統(tǒng)計(jì)信息進(jìn)行占空比計(jì)算,以保證并發(fā)垃圾收集器可以在堆占滿之前完成。不過,使用過去的行為預(yù)測(cè)將來的變化的估計(jì)方式可能并不總是足夠準(zhǔn)確,可能在某些情況下無(wú)法阻止堆用滿。如果需要收集的垃圾太多,可以嘗試下面這些步驟,一次使用一個(gè):

Step

Options

1. 增加保險(xiǎn)系數(shù)

-XX:CMSIncrementalSafetyFactor=<N>

2. 增加最小占空比

-XX:CMSIncrementalDutyCycleMin=<N>

3. 關(guān)閉自動(dòng)占空比計(jì)算,使用固定占空比

-XX:-CMSIncrementalPacing -XX:CMSIncrementalDutyCycle=<N>

7.10 測(cè)量

下面是使用-verbose:gc和-XX:+PrintGCDetails參數(shù)時(shí),并發(fā)垃圾收集器的輸出,一些小細(xì)節(jié)已經(jīng)被去掉了。注意,并發(fā)垃圾收集器的輸出里摻雜著小回收的輸出;典型情況下,很多小回收會(huì)發(fā)生在并發(fā)收集周期之中。其中的CMS-initial-mark表征了一個(gè)并發(fā)垃圾回收周期的開始。CMS-concurrent-mark: 標(biāo)志著并發(fā)標(biāo)記階段的完成,而CMS-concurrent-sweep則標(biāo)志著并發(fā)清除階段的完成。之前沒有提到過的預(yù)清除階段以CMS- concurrent-preclean為標(biāo)志。預(yù)清除可以和重標(biāo)記階段CMS-remark的準(zhǔn)備工作同時(shí)運(yùn)行。最后一個(gè)階段是CMS- concurrent-reset,這是下一個(gè)并發(fā)收集周期的準(zhǔn)備工作。

  1. [GC [1 CMS-initial-mark: 13991K(20288K)] 14103K(22400K), 0.0023781 secs]
  2. [GC [DefNew: 2112K->64K(2112K), 0.0837052 secs] 16103K->15476K(22400K), 0.0838519 secs]
  3. ...
  4. [GC [DefNew: 2077K->63K(2112K), 0.0126205 secs] 17552K->15855K(22400K), 0.0127482 secs]  
  5. [CMS-concurrent-mark: 0.267/0.374 secs]
  6. [GC [DefNew: 2111K->64K(2112K), 0.0190851 secs] 17903K->16154K(22400K), 0.0191903 secs]  
  7. [CMS-concurrent-preclean: 0.044/0.064 secs]  
  8. [GC [1 CMS-remark: 16090K(20288K)] 17242K(22400K), 0.0210460 secs]  
  9. [GC [DefNew: 2112K->63K(2112K), 0.0716116 secs] 18177K->17382K(22400K), 0.0718204 secs]  
  10. [GC [DefNew: 2111K->63K(2112K), 0.0830392 secs] 19363K->18757K(22400K), 0.0832943 secs]  
  11. ...  
  12. [GC [DefNew: 2111K->0K(2112K), 0.0035190 secs] 17527K->15479K(22400K), 0.0036052 secs]  
  13. [CMS-concurrent-sweep: 0.291/0.662 secs]  
  14. [GC [DefNew: 2048K->0K(2112K), 0.0013347 secs] 17527K->15479K(27912K), 0.0014231 secs]  
  15. [CMS-concurrent-reset: 0.016/0.016 secs]  
  16. [GC [DefNew: 2048K->1K(2112K), 0.0013936 secs] 17527K->15479K(27912K), 0.0014814 secs] 

初始標(biāo)記停頓在典型情況下比小回收的停頓時(shí)間還要小。而如上例所示,并發(fā)階段(并發(fā)標(biāo)記、并發(fā)預(yù)清除和并發(fā)清除)通常會(huì)比小回收長(zhǎng)很多。不過注意,應(yīng)用并沒有在這些并發(fā)階段中停頓下來。重標(biāo)記停頓通常和一個(gè)小回收的長(zhǎng)度相當(dāng)。重標(biāo)記停頓揮手道應(yīng)用的某些特征(如高對(duì)象修改頻率可能會(huì)增加這個(gè)停頓)和上一次小回收的時(shí)間(即,更多的年輕代對(duì)象可能會(huì)增加這個(gè)停頓)的影響。

8. 其他考慮

8.1 永久代尺寸

在大部分應(yīng)用中,永久代對(duì)于垃圾回收性能沒有顯著的影響。不過,一些應(yīng)用會(huì)動(dòng)態(tài)的生成與加載很多類;比如,一些 JavaServer Pages(JSP)頁(yè)面的實(shí)現(xiàn)。這些應(yīng)用可能需要很大的永久代去存放一些多余的類。如果這樣的話,最大永久代的尺寸可以用命令行參數(shù) -XX:MaxPermSize=<N>來增大。

8.2 Finalization; Weak, Soft and Phantom References

一些應(yīng)用使用 finalization 和 weak, soft, phantom 引用與垃圾收集器交互。這些特征可以 Java 語(yǔ)言層帶來性能影響。一個(gè)例子是通過 finalization 來關(guān)閉文件描述符,這會(huì)導(dǎo)致一個(gè)外部資源依賴于垃圾收集器。以來垃圾收集器來管理內(nèi)存之外的資源是個(gè)壞主意。

參考資料章節(jié)中的文章深度討論了一些finalization的常見錯(cuò)誤和用來避免這些錯(cuò)誤的技術(shù)。

8.3 顯式垃圾回收

應(yīng)用程序和垃圾回收器的另一個(gè)交互途徑是顯式調(diào)用 System.gc() 進(jìn)行完整的垃圾回收。這回強(qiáng)制進(jìn)行一次主回收,即使沒有必要(也就是說一次小回收可能就足夠了),所以應(yīng)該避免這種情況。顯式垃圾回收對(duì)性能的影響可以通過使用 -XX:+DisableExplicitGC 進(jìn)行比較來進(jìn)行測(cè)量,這樣虛擬機(jī)會(huì)無(wú)視 System.gc() 的。

最常見的顯式調(diào)用垃圾回收的場(chǎng)景是 RMI 的分布式垃圾回收。使用 RMI 的應(yīng)用會(huì)引用到其他虛擬機(jī)中的對(duì)象。在這種分布式應(yīng)用的場(chǎng)景下,本地堆中的垃圾可能不能被回收掉,所以 RMI 會(huì)周期性強(qiáng)制進(jìn)行完整的垃圾回收。這些回收的頻率可以使用參數(shù)來控制。如

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

這里指定了垃圾回收每小時(shí)運(yùn)行一次,而不是缺省的每分鐘一次。不過,這可能會(huì)導(dǎo)致某些對(duì)象的清除消耗太長(zhǎng)時(shí)間。這些參數(shù)可以被設(shè)置到高達(dá)Long.MAX_VALUE來讓顯式垃圾回收的間隔時(shí)間無(wú)限長(zhǎng),如果沒有合適的DGC上限時(shí)間的話。

8.4 Soft References

Soft reference在虛擬機(jī)中比在客戶集中存活的更長(zhǎng)一些。其清除頻率可以用命令行參數(shù) -XX:SoftRefLRUPolicyMSPerMB=<N>來控制,這可以指定每兆堆空閑空間的 soft reference 保持存活(一旦它不強(qiáng)可達(dá)了)的毫秒數(shù),這意味著每兆堆中的空閑空間中的 soft reference 會(huì)(在最后一個(gè)強(qiáng)引用被回收之后)存活1秒鐘。注意,這是一個(gè)近似的值,因?yàn)?soft reference 只會(huì)在垃圾回收時(shí)才會(huì)被清除,而垃圾回收并不總在發(fā)生。

8.5 Solaris 8 替換 libthread

Solaris 8 操作系統(tǒng)提供了一個(gè)替代的線程庫(kù),libthread, 它將線程直接綁定成了輕量級(jí)進(jìn)程(LWP)。有些應(yīng)用能夠從中極大獲益,并潛在的對(duì)所有多線程應(yīng)用都或多或少的有好處。下面的命令會(huì)為 java 啟用替換的 libthread(BASH 格式)

LD_PRELOAD=/usr/lib/lwp/libthread.so.1
export LD_PRELOAD
java ...

這個(gè)方法僅對(duì) Solaris 8 適用,因?yàn)閷?duì) Solaris 9 操作系統(tǒng)來說,這是缺省的,而 Solaris 10 中,這是惟一的線程庫(kù)。

9. 相關(guān)資源

  1. HotSpot VM Frequently Asked Questions (FAQ)
  2. GC output examples 介紹了如何解釋不同垃圾收集器的輸出。
  3. How to Handle Java Finalization’s Memory-Retention Issues 介紹了一些容易犯的錯(cuò)誤和避免他們的方法。
  4. Richard Jones and Rafael Lins, Garbage Collection: Algorithms for Automated Dynamic Memory Management, Wiley and Sons (1996), ISBN 0-471-94148-4

在本網(wǎng)站中,名詞“Java Virtual Machine” 和“JVM” 都代表 Java 平臺(tái)虛擬機(jī)。

英文原文:http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html

本文來自:http://wangxu.me/blog/p/209

 

責(zé)任編輯:林師授 來源: wangxu.me
相關(guān)推薦

2015-07-06 10:14:25

Java垃圾回收實(shí)戰(zhàn)

2014-12-19 11:07:40

Java

2012-01-09 16:53:36

JavaJVM

2020-04-22 21:44:18

Java虛擬機(jī)算法

2023-11-23 09:26:50

Java調(diào)優(yōu)

2021-10-05 20:29:55

JVM垃圾回收器

2012-01-10 11:19:35

JavaJVM

2010-09-26 11:22:22

JVM垃圾回收JVM

2012-01-09 17:06:16

JavaJVM

2010-11-05 09:47:11

OracleJava虛擬機(jī)

2020-05-14 13:39:19

Java 垃圾回收機(jī)制

2021-02-04 10:43:52

開發(fā)技能代碼

2021-09-10 00:34:22

Java 線程啟動(dòng)

2012-01-10 14:25:36

JavaJVM

2020-09-02 07:03:04

虛擬機(jī)HotSpotJava

2025-01-24 00:00:00

JavaHotSpot虛擬機(jī)

2021-01-04 10:08:07

垃圾回收Java虛擬機(jī)

2023-02-26 11:50:04

Hbase程序Oracle

2009-06-18 13:59:33

Java SE 6垃圾回收器

2020-12-10 16:11:17

Java開發(fā)代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)