【賽迪網(wǎng)-IT技術(shù)報(bào)道】?jī)?nèi)存管理是影響軟件應(yīng)用程序性能的一個(gè)重要因素。與實(shí)際的數(shù)據(jù)計(jì)算時(shí)間相比,分配和卸載內(nèi)存所用的時(shí)間更長(zhǎng)。
雖然C++可對(duì)內(nèi)存分配與釋放進(jìn)行直接控制,Java利用垃圾收集來(lái)回收程序不再需要的內(nèi)存,試圖掌握內(nèi)存管理。但是,在需要實(shí)時(shí)性能時(shí),與垃圾收集有關(guān)的“暫停”一直是人們反對(duì)應(yīng)對(duì)Java的中心論點(diǎn)。
垃圾收集是一個(gè)周期性的過(guò)程,它中斷程序的正常執(zhí)行,分析對(duì)象引用,并回收被分配但不再被引用訪問(wèn)的內(nèi)存。在大型Java應(yīng)用程序中,垃圾收集暫停可能持續(xù)幾秒鐘,這段時(shí)間足以中斷任何類(lèi)型的實(shí)時(shí)通信或控制系統(tǒng)。
因此,垃圾收集提供的內(nèi)存提取要求一些開(kāi)發(fā)者更仔細(xì)地考慮內(nèi)存管理問(wèn)題。即使Java并沒(méi)有提供和C++同等級(jí)別的內(nèi)存分配控制,編程模式仍然會(huì)對(duì)Java應(yīng)用程序的內(nèi)存性能產(chǎn)生重大影響。
在本文中,我將簡(jiǎn)單回顧一下Java 5.0的垃圾收集調(diào)整功能。
Java 5.0垃圾收集原理
Java 1.5新特性??工效學(xué)??的目標(biāo)是通過(guò)少的命令行調(diào)整,為JVM提供優(yōu)良的性能。工效學(xué)試圖為一個(gè)應(yīng)用程序選擇的垃圾收集器、堆大小與運(yùn)行時(shí)間編譯器。
垃圾收集器的選擇何時(shí)會(huì)對(duì)用戶(hù)產(chǎn)生影響呢?對(duì)許多應(yīng)用程序來(lái)說(shuō),它根本沒(méi)有影響。也就是說(shuō),在垃圾收集產(chǎn)生的暫停的頻率與持續(xù)時(shí)間適度的情況下,應(yīng)用程序可在其規(guī)范內(nèi)執(zhí)行。如果一個(gè)大型應(yīng)用程序出現(xiàn)擴(kuò)充,產(chǎn)生大量線程、處理器、套接字和許多內(nèi)存,就會(huì)出現(xiàn)例外。
如果一個(gè)對(duì)象再也不能通過(guò)運(yùn)行程序中的任何指針到達(dá),則視其為垃圾。直接的垃圾收集運(yùn)算法則簡(jiǎn)單地在每個(gè)可到達(dá)的對(duì)象間迭代。那么,剩下的對(duì)象即為垃圾。這一方法所用的時(shí)間與活動(dòng)對(duì)象的數(shù)目成比例關(guān)系,且禁止用于維護(hù)許多活動(dòng)數(shù)據(jù)的大型應(yīng)用程序。
從Java 2開(kāi)始,虛擬機(jī)合并了許多應(yīng)用分代收集組合的各種收集運(yùn)算法則。盡管簡(jiǎn)單的垃圾收集檢查堆中的每一個(gè)活動(dòng)對(duì)象,但分代收集利用多數(shù)應(yīng)用程序的幾個(gè)憑經(jīng)驗(yàn)觀察得到的特性來(lái)避免額外工作。這些觀察得到的特性中為重要的一個(gè)就是所謂的早期失效率。許多對(duì)象分配以后很快“已經(jīng)死亡”。例如,迭代器對(duì)象僅在單獨(dú)循環(huán)中存活。為優(yōu)化這種情況,我們對(duì)內(nèi)存進(jìn)行分代管理,或在內(nèi)存池中保留不同年齡的對(duì)象。當(dāng)一代裝滿(mǎn)時(shí),就對(duì)這個(gè)代進(jìn)行垃圾收集。對(duì)象被分配到更年齡對(duì)象代,或新生代中。由于早期失效率,多數(shù)對(duì)象在那里死亡。
如果垃圾收集器成為瓶頸,你可能希望自定義代的大小。詳細(xì)檢查垃圾收集器的輸出,然后探究單個(gè)性能計(jì)量單位對(duì)垃圾收集器參數(shù)的靈敏度。
初始化時(shí),保留一個(gè)的地址空間,在必要時(shí)才分配給物理內(nèi)存。為對(duì)象內(nèi)存保留的全部地址空間可分為新生代和舊生代。新生代由eden和兩個(gè)生存空間組成。對(duì)象初分配到eden中。任何時(shí)候,一個(gè)生存空間為空,并作為下一個(gè)空間的目的地,在eden與另一個(gè)生存空間中復(fù)制活動(dòng)對(duì)象的集合。對(duì)象以這種方式在生存空間中復(fù)制,直到它們老化,或復(fù)制到舊生代中。與舊生代關(guān)系密切的第三個(gè)代稱(chēng)為永生代。這是一個(gè)特別的代,因?yàn)樗A籼摂M機(jī)所需要的數(shù)據(jù),來(lái)描述在Java語(yǔ)言中沒(méi)有等同物的對(duì)象。例如,描述類(lèi)與方法的對(duì)象存儲(chǔ)在永生代中。
性能因素
Java應(yīng)用程序(特別是垃圾收集)有兩個(gè)性能計(jì)量單位:吞吐量與暫停。吞吐量是指在一段較長(zhǎng)時(shí)間內(nèi),沒(méi)有用于垃圾收集的時(shí)間百分比。吞吐量包括用于分配的時(shí)間(但用于調(diào)整分配速度的時(shí)間一般不包括在內(nèi))。暫停是應(yīng)用程序因?yàn)槔占霈F(xiàn)的停頓時(shí)間。
一些用戶(hù)還對(duì)其他因素較為敏感。例如,占用率(footprint) 是一批工作進(jìn)程的集合,以頁(yè)和緩沖行數(shù)計(jì)量,在物理內(nèi)存有限或者有很多進(jìn)程的系統(tǒng)中,占用率可表示擴(kuò)展性。
反應(yīng)性(Promptness)是對(duì)象死去的時(shí)間和內(nèi)存變?yōu)榭捎脮r(shí)的時(shí)間差,是分布系統(tǒng),包括遠(yuǎn)程方法調(diào)用(RMI)中的重要因素。
通常來(lái)說(shuō),特定的代大小選擇這些因素之間的平衡作用。例如,一個(gè)非常大的新生代的吞吐量可以,但這要以犧牲占用率、反應(yīng)性和暫停時(shí)間為代價(jià)。你也可以犧牲吞吐量,應(yīng)用一個(gè)小型的新生代來(lái)使新生代暫停時(shí)間短。
如果你希望提高有大量處理器的應(yīng)用程序的性能,你應(yīng)該使用吞吐量收集器。你可以用命令行標(biāo)記-XX:+UseParallelGC來(lái)激活吞吐量收集器。你可以用ParallelGCThreads選項(xiàng)-XX:ParallelGCThreads=來(lái)控制垃圾收集器線程的數(shù)量。
暫停時(shí)間目標(biāo)用命令行標(biāo)記-XX:MaxGCPauseMillis=來(lái)指定,這是對(duì)吞吐量收集器的一個(gè)暗示,即它需要毫秒或更短的暫停時(shí)間。存在有許多調(diào)整代大小的選項(xiàng),如-XX:YoungGenerationSizeIncrement=用于新生代;而-XX:TenuredGenerationSizeIncrement=用于舊生代。
如果應(yīng)用程序受益于較短的垃圾收集器暫停,且能夠在應(yīng)用程序運(yùn)行時(shí)與垃圾收集器共享處理器資源,我建議使用并行低暫停收集器。如果舊生代占用率超出初始占用率(即當(dāng)前堆的百分比用于并發(fā)收集啟動(dòng)之前),并發(fā)收集將啟動(dòng)。
默認(rèn)的初始占用率約為68%。你可以用參數(shù)-XX:CMSInitiatingOccupancyFraction=進(jìn)行設(shè)置,這里的是當(dāng)前舊生代大小百分比。你能夠以并發(fā)階段遞增完成的方式使用并發(fā)收集器。這種模式(這里稱(chēng)之為”i-cms”)將收集器并發(fā)完成的工作分割成時(shí)間小段,安排在新生代收集之間。當(dāng)需要并發(fā)收集器提供短暫停時(shí)間的應(yīng)用程序在擁有少量處理器的機(jī)器上運(yùn)行時(shí),這一特性很有幫助。
微調(diào)垃圾收集
命令行參數(shù)-verbose:gc打印每個(gè)收集階段的信息。如果打開(kāi)此參數(shù),你會(huì)在每個(gè)垃圾收集階段看到相似的輸出結(jié)果。例如:
|
有兩個(gè)次要收集和一個(gè)主要收集(完全GC)。標(biāo)記-XX:+PrintGCDetails打印收集的其他信息。標(biāo)記-XX:+PrintGCTimeStamps打印每次收集開(kāi)始的時(shí)間。列表A是兩個(gè)標(biāo)記被設(shè)定后的結(jié)果。
另外,主要收集的信息由舊生代描述。在這里,舊生代使用率減少到約10%,所用時(shí)間約為0.13秒。
許多參數(shù)影響代的大小。在虛擬機(jī)的初始階段,堆的整個(gè)空間被保留。你可以用-Xmx選項(xiàng)指定保留空間的大小。如果-Xms參數(shù)的值小于-Xmx參數(shù)的值,表示不是所有的保留空間立即提交給虛擬機(jī)。堆的不同部分(永生代、舊生代和新生代)必要時(shí)可能會(huì)超出虛擬空間的限制。
默認(rèn)情況下,在每次收集時(shí),虛擬機(jī)增大或縮小堆的大小,努力將自由空間與活動(dòng)對(duì)象的比例保持在一個(gè)特定的范圍內(nèi)。這個(gè)范圍是一個(gè)百分?jǐn)?shù),由參數(shù)-XX:MinHeapFreeRatio=和-XX:MaxHeapFreeRatio=設(shè)定,-Xms為下限,-Xmx為上限。除非有暫停問(wèn)題,否則向虛擬機(jī)許可盡可能多的內(nèi)存。默認(rèn)的大小(64MB)一般都太小。你可以在Sun網(wǎng)站找到其他VM選項(xiàng)的說(shuō)明。
你還可以為新生代的堆設(shè)定一個(gè)比例。新生代越大,次要收集就越少發(fā)生。但是,在一個(gè)有限大小的堆中,新生代越大就意味著舊生代越小,這會(huì)增加主要收集的頻率。選擇要根據(jù)應(yīng)用程序分配的對(duì)象的壽命分配來(lái)決定。
新生代的大小由NewRatio控制。例如,設(shè)定-XX:NewRatio=3說(shuō)明新生代與舊生代之比為1:3。如果希望的話,可用參數(shù)SurvivorRatio來(lái)調(diào)整生存空間的大小,但它并不如性能重要。
例如,-XX:SurvivorRatio=6說(shuō)明每個(gè)生存空間與eden的比為1:6。除非你發(fā)現(xiàn)過(guò)多主要收集或暫停時(shí)間問(wèn)題,向新生代許可大量?jī)?nèi)存。
Java 5.0執(zhí)行了三個(gè)不同的垃圾收集器。吞吐量收集器使用新生代收集器的并行版本。如果-XX:+UseParallelGC選項(xiàng)傳遞給命令行,則使用并行收集器;如果-Xincgc或-XX:+UseConcMarkSweepGC選項(xiàng)傳遞給命令行,則使用并發(fā)收集器。
在這種情況下,收集過(guò)程中應(yīng)用程序會(huì)出現(xiàn)短期暫停。如果-XX:+UseTrainGC傳遞經(jīng)命令行,則使用遞增式低暫停收集器。將來(lái)版本將不支持這一功能,但如果你想了解更多信息,請(qǐng)?jiān)L問(wèn)Sun的關(guān)于應(yīng)用這個(gè)收集器的文件資料。(提示:不要同時(shí)使用-XX:+UseParallelGC和-XX:+UseConcMarkSweepGC。)
結(jié)論
根據(jù)應(yīng)用程序的需求,垃圾收集可能成為各種應(yīng)用程序的瓶頸。通過(guò)了解應(yīng)用程序需求與垃圾收集選項(xiàng),可以減輕垃圾收集的影響。