欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java虛擬機(jī)JVM性能優(yōu)化(三):垃圾收集詳解

 更新時(shí)間:2014年09月19日 11:44:10   投稿:junjie  
這篇文章主要介紹了Java虛擬機(jī)JVM性能優(yōu)化(三):垃圾收集詳解,本文講解了眾多的JVM垃圾收集器知識(shí)點(diǎn),需要的朋友可以參考下

Java平臺(tái)的垃圾收集機(jī)制顯著提高了開(kāi)發(fā)者的效率,但是一個(gè)實(shí)現(xiàn)糟糕的垃圾收集器可能過(guò)多地消耗應(yīng)用程序的資源。在Java虛擬機(jī)性能優(yōu)化系列的第三部分,Eva Andreasson向Java初學(xué)者介紹了Java平臺(tái)的內(nèi)存模型和垃圾收集機(jī)制。她解釋了為什么碎片化(而不是垃圾收集)是Java應(yīng)用程序性能的主要問(wèn)題所在,以及為什么分代垃圾收集和壓縮是目前處理Java應(yīng)用程序碎片化的主要辦法(但不是最有新意的)。

垃圾收集(GC)的目的是釋放那些不再被任何活動(dòng)對(duì)象引用的Java對(duì)象所占用的內(nèi)存,它是Java虛擬機(jī)動(dòng)態(tài)內(nèi)存管理機(jī)制的核心部分。在一個(gè)典型的垃圾收集周期里,所有仍然被引用的對(duì)象(因此是可達(dá)的)都將被保留,而那些不再被引用的對(duì)象將被釋放、其所占用的空間將被回收用來(lái)分配給新的對(duì)象。

為了理解垃圾收集機(jī)制和各種垃圾收集算法,首先需要知道關(guān)于Java平臺(tái)內(nèi)存模型的一些知識(shí)。

垃圾收集和Java平臺(tái)內(nèi)存模型

當(dāng)用命令行啟動(dòng)一個(gè)Java程序并指定啟動(dòng)參數(shù)-Xmx時(shí)(例如:java -Xmx:2g MyApp),指定大小的內(nèi)存就分配給了Java進(jìn)程,這就是所謂的Java堆。這個(gè)專(zhuān)用的內(nèi)存地址空間用于存儲(chǔ)Java程序(有時(shí)是JVM)所創(chuàng)建的對(duì)象。隨著應(yīng)用程序運(yùn)行并不斷為新對(duì)象分配內(nèi)存,Java堆(即專(zhuān)門(mén)的內(nèi)存地址空間)就會(huì)慢慢被填滿(mǎn)。

最終Java堆會(huì)被填滿(mǎn),也就是說(shuō)內(nèi)存分配線(xiàn)程找不到一塊足夠大的連續(xù)空間為新對(duì)象分配內(nèi)存,這時(shí)JVM決定要通知垃圾收集器并啟動(dòng)垃圾收集。垃圾收集也可以通過(guò)在程序中調(diào)用System.gc()來(lái)觸發(fā),但使用System.gc()并不能確保垃圾收集一定被執(zhí)行。在任何一次垃圾收集之前,垃圾收集機(jī)制都會(huì)首先判斷執(zhí)行垃圾收集是否安全,當(dāng)應(yīng)用程序的所有活動(dòng)線(xiàn)程都處于安全點(diǎn)時(shí)就可以開(kāi)始執(zhí)行一次垃圾收集。例如:當(dāng)正在為對(duì)象分配內(nèi)存時(shí)就不能執(zhí)行垃圾收集,或者是正在優(yōu)化CPU指令時(shí)也不能執(zhí)行垃圾收集,因?yàn)檫@樣很可能會(huì)丟失上下文從而搞錯(cuò)最終結(jié)果。

垃圾收集器不能回收任何一個(gè)有活動(dòng)引用的對(duì)象,那將破壞Java虛擬機(jī)規(guī)范。也無(wú)需立即回收死對(duì)象,因?yàn)樗缹?duì)象最終還是會(huì)被后續(xù)的垃圾收集所回收。盡管有很多種垃圾收集的實(shí)現(xiàn)方法,但以上兩點(diǎn)對(duì)所有垃圾收集實(shí)現(xiàn)都是相同的。垃圾收集真正的挑戰(zhàn)在于如何識(shí)別對(duì)象是否存活以及如何在盡量不影響應(yīng)用程序的情況下回收內(nèi)存,因此垃圾收集器的目標(biāo)有以下兩個(gè):

1.迅速釋放沒(méi)有引用的內(nèi)存以滿(mǎn)足應(yīng)用程序的內(nèi)存分配需要從而避免內(nèi)存溢出。
2.回收內(nèi)存時(shí)對(duì)正在運(yùn)行的應(yīng)用程序性能(延遲和吞吐量)的影響最小化。

兩類(lèi)垃圾收集

在本系列的第一篇中,我介紹了兩種垃圾收集的方法,即引用計(jì)數(shù)和跟蹤收集。接下來(lái)我們進(jìn)一步探討這兩種方法,并介紹一些在生產(chǎn)環(huán)境中使用的跟蹤收集算法。

引用計(jì)數(shù)收集器

引用計(jì)數(shù)收集器記錄了指向每個(gè)Java對(duì)象的引用數(shù),一旦指向某個(gè)對(duì)象的引用數(shù)為0,那么就可以立即回收該對(duì)象。這種即時(shí)性是引用計(jì)數(shù)收集器的主要優(yōu)點(diǎn),而且維護(hù)那些沒(méi)有引用指向的內(nèi)存幾乎沒(méi)有開(kāi)銷(xiāo),不過(guò)為每個(gè)對(duì)象記錄最新的引用數(shù)卻是代價(jià)高昂的。

引用計(jì)數(shù)收集器的主要難點(diǎn)在于如何保證引用計(jì)數(shù)的準(zhǔn)確性,另外一個(gè)眾所周知的難點(diǎn)是如何處理循環(huán)引用的情況。如果兩個(gè)對(duì)象彼此引用,而且沒(méi)有被其他活動(dòng)對(duì)象所引用,那么這兩個(gè)對(duì)象的內(nèi)存永遠(yuǎn)都不會(huì)被回收,因?yàn)橹赶蜻@兩個(gè)對(duì)象的引用數(shù)都不為0。對(duì)循環(huán)引用結(jié)構(gòu)的內(nèi)存回收需要major analysis(譯者注:Java堆上的全局分析),這將增加算法的復(fù)雜性,從而也給應(yīng)用程序帶來(lái)額外的開(kāi)銷(xiāo)。

跟蹤收集器

跟蹤收集器基于這樣的假設(shè):所有的活動(dòng)對(duì)象都可以通過(guò)一個(gè)已知的初始活動(dòng)對(duì)象集合的迭代引用(引用以及引用的引用)找到??梢酝ㄟ^(guò)分析寄存器、全局對(duì)象和棧幀來(lái)確定初始活動(dòng)對(duì)象集合(也被稱(chēng)為根對(duì)象)。確定了初始對(duì)象集合后,跟蹤收集器順著這些對(duì)象的引用關(guān)系依次將引用所指向的對(duì)象標(biāo)注為活動(dòng)對(duì)象,就這樣已知的活動(dòng)對(duì)象集合不斷擴(kuò)大。這一過(guò)程持續(xù)進(jìn)行直到所有被引用的對(duì)象都被標(biāo)注為活動(dòng)對(duì)象,而那些沒(méi)有被標(biāo)注過(guò)的對(duì)象的內(nèi)存就被回收。

跟蹤收集器不同于引用計(jì)數(shù)收集器主要在于它可以處理循環(huán)引用結(jié)構(gòu)。多數(shù)的跟蹤收集器都是在標(biāo)記階段發(fā)現(xiàn)那些循環(huán)引用結(jié)構(gòu)中的無(wú)引用對(duì)象。

跟蹤收集器是動(dòng)態(tài)語(yǔ)言中最常用的內(nèi)存管理方式,也是目前Java中最常見(jiàn)的方式,同時(shí)在生產(chǎn)環(huán)境中也被驗(yàn)證了很多年。下面我將從實(shí)現(xiàn)跟蹤收集的一些算法開(kāi)始介紹跟蹤收集器。

跟蹤收集算法

復(fù)制垃圾收集器和標(biāo)記-清除垃圾收集器并不是什么新東西,但它們?nèi)匀皇悄壳皩?shí)現(xiàn)跟蹤收集的兩種最常見(jiàn)算法。

復(fù)制垃圾收集器

傳統(tǒng)的復(fù)制垃圾收集器使用堆中的兩個(gè)地址空間(即from空間和to空間),當(dāng)執(zhí)行垃圾收集時(shí)from空間的活動(dòng)對(duì)象被復(fù)制到to空間,當(dāng)from空間的所有活動(dòng)對(duì)象都被移出(譯者注:復(fù)制到to空間或者老年代)后,就可以回收整個(gè)from空間了,當(dāng)再次開(kāi)始分配空間時(shí)將首先使用to空間(譯者注:即上一輪的to空間作為新一輪的from空間)。

在該算法的早期實(shí)現(xiàn)中,from空間和to空間不斷變換位置,也就是說(shuō)當(dāng)to空間滿(mǎn)了,觸發(fā)了垃圾收集,to空間就成為了from空間,如圖1所示。

圖1 傳統(tǒng)的復(fù)制垃圾收集順序

最新的復(fù)制算法允許堆內(nèi)任意地址空間作為to空間和from空間。這樣它們不需要彼此交換位置,而只是邏輯上變換了位置。

復(fù)制收集器的優(yōu)點(diǎn)是在to空間被復(fù)制的對(duì)象緊湊排列,完全沒(méi)有碎片。而碎片化正是其他垃圾收集器所面臨的一個(gè)共同問(wèn)題,也是我之后主要討論的問(wèn)題。

復(fù)制收集器的缺陷

通常來(lái)說(shuō)復(fù)制收集器是stop-the-world的,也就是說(shuō)只要垃圾收集在進(jìn)行,應(yīng)用程序就無(wú)法執(zhí)行。對(duì)于這種實(shí)現(xiàn)來(lái)說(shuō),你需要復(fù)制的東西越多,對(duì)應(yīng)用程序性能的影響就越大。對(duì)于那些響應(yīng)時(shí)間敏感的應(yīng)用來(lái)說(shuō)這是個(gè)缺點(diǎn)。使用復(fù)制收集器時(shí),你還要考慮最壞的場(chǎng)景(即from空間中的所有對(duì)象都是活動(dòng)對(duì)象),這時(shí)你需要為移動(dòng)這些活動(dòng)對(duì)象準(zhǔn)備足夠大的空間,因此to空間必須大到可以裝下from空間的所有對(duì)象。由于這個(gè)限制,復(fù)制算法的內(nèi)存利用率稍有不足(譯者注:在最壞的情況下to空間需要和from空間大小相同,所以只有50%的利用率)。

標(biāo)記-清除收集器

部署在企業(yè)生產(chǎn)環(huán)境上的大多數(shù)商業(yè)JVM采用的都是標(biāo)記-清除(或者叫標(biāo)記)收集器,因?yàn)樗鼪](méi)有復(fù)制垃圾收集器對(duì)應(yīng)用程序性能的影響問(wèn)題。其中最有名的標(biāo)記收集器包括CMS、G1、GenPar和DeterministicGC。

標(biāo)記-清除收集器跟蹤對(duì)象引用,并且用標(biāo)志位將每個(gè)找到的對(duì)象標(biāo)記為live。這個(gè)標(biāo)志位通常對(duì)應(yīng)堆上的一個(gè)地址或是一組地址。例如:活動(dòng)位可以是對(duì)象頭的一個(gè)位(譯者注:bit)或是一個(gè)位向量、一個(gè)位圖。

在標(biāo)記完成之后就進(jìn)入了清除階段。清除階段通常都會(huì)再次遍歷堆(不僅是標(biāo)記為live的對(duì)象,而是整個(gè)堆),用來(lái)定位那些沒(méi)有標(biāo)記的連續(xù)內(nèi)存地址空間(沒(méi)有被標(biāo)記的內(nèi)存就是空閑并可回收的),然后收集器將它們整理為空閑列表。垃圾收集器可以有多個(gè)空閑列表(通常按照內(nèi)存塊的大小劃分),有些JVM(例如:JRockit Real Time)的收集器甚至基于應(yīng)用程序的性能分析和對(duì)象大小的統(tǒng)計(jì)結(jié)果來(lái)動(dòng)態(tài)劃分空閑列表。

清除階段過(guò)后,應(yīng)用程序就可以再次分配內(nèi)存了。從空閑列表中為新對(duì)象分配內(nèi)存時(shí),新分配的內(nèi)存塊需要符合新對(duì)象的大小,或是線(xiàn)程的平均對(duì)象大小,或是應(yīng)用程序的TLAB大小。為新對(duì)象找到大小合適的內(nèi)存塊有助于優(yōu)化內(nèi)存和減少碎片。

標(biāo)記-清除收集器的缺陷

標(biāo)記階段的執(zhí)行時(shí)間依賴(lài)于堆中活動(dòng)對(duì)象的數(shù)量,而清除階段的執(zhí)行時(shí)間依賴(lài)于堆的大小。因此對(duì)于堆設(shè)置較大并且堆中活動(dòng)對(duì)象較多的情況,標(biāo)記-清除算法會(huì)有一定的暫停時(shí)間。

對(duì)于內(nèi)存消耗很大的應(yīng)用程序來(lái)說(shuō),你可以調(diào)整垃圾收集參數(shù)以適應(yīng)各種應(yīng)用程序的場(chǎng)景和需要。在很多情況下,這種調(diào)整至少推遲了標(biāo)記階段/清除階段給應(yīng)用程序或服務(wù)協(xié)議SLA(SLA這里指應(yīng)用程序要達(dá)到的響應(yīng)時(shí)間)帶來(lái)的風(fēng)險(xiǎn)。但是調(diào)優(yōu)僅僅對(duì)特定的負(fù)載和內(nèi)存分配率有效,負(fù)載變化或是應(yīng)用程序本身的修改都需要重新調(diào)優(yōu)。

標(biāo)記-清除收集器的實(shí)現(xiàn)

至少有兩種已經(jīng)在商業(yè)上驗(yàn)證的方法來(lái)實(shí)現(xiàn)標(biāo)記-清除垃圾收集。一種是并行垃圾收集,另一種是并發(fā)(或者多數(shù)時(shí)間是并發(fā))垃圾收集。

并行收集器

并行收集是指資源被垃圾收集線(xiàn)程并行使用。大多數(shù)并行收集的商業(yè)實(shí)現(xiàn)都是stop-the-world收集器,即所有的應(yīng)用程序線(xiàn)程都暫停直到完成一次垃圾收集,因?yàn)槔占骺梢愿咝У厥褂觅Y源,所以通常會(huì)在吞吐量的基準(zhǔn)測(cè)試中得到高分,如SPECjbb。如果吞吐量對(duì)你的應(yīng)用程序至關(guān)重要,那么并行垃圾收集器是一個(gè)很好的選擇。

并行收集的主要代價(jià)(特別是對(duì)于生產(chǎn)環(huán)境)是應(yīng)用程序線(xiàn)程在垃圾收集期間無(wú)法正常工作,就像復(fù)制收集器一樣。因此那些對(duì)于響應(yīng)時(shí)間敏感的應(yīng)用程序使用并行收集器會(huì)有很大的影響。特別是在堆空間中有很多復(fù)雜的活動(dòng)對(duì)象結(jié)構(gòu)時(shí),有很多的對(duì)象引用需要跟蹤。(還記得嗎標(biāo)記-清除收集器回收內(nèi)存的時(shí)間取決于跟蹤活動(dòng)對(duì)象集合的時(shí)間加上遍歷整個(gè)堆的時(shí)間)對(duì)于并行方法來(lái)說(shuō),整個(gè)垃圾收集時(shí)間應(yīng)用程序都會(huì)暫停。

并發(fā)收集器

并發(fā)垃圾收集器更適合那些對(duì)響應(yīng)時(shí)間敏感的應(yīng)用程序。并發(fā)意味著垃圾收集線(xiàn)程和應(yīng)用程序線(xiàn)程并發(fā)執(zhí)行。垃圾收集線(xiàn)程并不獨(dú)占所有資源,因此需要決定何時(shí)開(kāi)始一次垃圾收集,需要有足夠的時(shí)間跟蹤活動(dòng)對(duì)象集合并在應(yīng)用程序內(nèi)存溢出前回收內(nèi)存。如果垃圾收集沒(méi)有及時(shí)完成,應(yīng)用程序就會(huì)拋出內(nèi)存溢出錯(cuò)誤,另一方面又不希望垃圾收集執(zhí)行時(shí)間太長(zhǎng)因?yàn)槟菢訒?huì)消耗應(yīng)用程序的資源進(jìn)而影響吞吐量。保持這種平衡是需要技巧的,因此在確定開(kāi)始垃圾收集的時(shí)機(jī)以及選擇垃圾收集優(yōu)化的時(shí)機(jī)時(shí)都使用了啟發(fā)式算法。

另一個(gè)難點(diǎn)在于確定何時(shí)可以安全執(zhí)行一些操作(需要完整準(zhǔn)確的堆快照的操作),例如:需要知道何時(shí)標(biāo)記階段完成,這樣就可以進(jìn)入清理階段。對(duì)于stop-the-world的并行收集器來(lái)說(shuō)這不成問(wèn)題,因?yàn)槭澜缫呀?jīng)暫停了(譯者注:應(yīng)用程序線(xiàn)程暫停,垃圾收集線(xiàn)程獨(dú)占資源)。但對(duì)于并發(fā)收集器而言,從標(biāo)記階段立刻切換到清理階段可能不安全。如果應(yīng)用程序線(xiàn)程修改了一段內(nèi)存,而這段內(nèi)存已經(jīng)被垃圾收集器跟蹤并標(biāo)注過(guò)了,這就可能產(chǎn)生了新的沒(méi)有標(biāo)注的引用。在一些并發(fā)收集實(shí)現(xiàn)中,這會(huì)使應(yīng)用程序陷入長(zhǎng)時(shí)間重復(fù)標(biāo)注的循環(huán),當(dāng)應(yīng)用程序需要這段內(nèi)存時(shí)也無(wú)法獲得空閑內(nèi)存。

通過(guò)到目前為止的討論我們知道有很多的垃圾收集器和垃圾收集算法,分別適合特定的應(yīng)用程序類(lèi)型和不同的負(fù)載。不僅是不同的算法,還有不同的算法實(shí)現(xiàn)。所以在指定垃圾收集器錢(qián)最好了解應(yīng)用程序的需求以及自身特點(diǎn)。接下來(lái)我們將介紹Java平臺(tái)內(nèi)存模型的一些陷阱,這里陷阱的意思是,在動(dòng)態(tài)變化的生產(chǎn)環(huán)境中Java程序員容易做出的一些使得應(yīng)用程序性能變得更差的假設(shè)。

為什么調(diào)優(yōu)無(wú)法代替垃圾收集

多數(shù)的Java程序員都知道如果要優(yōu)化Java程序可以有很多選擇。若干個(gè)可選的JVM、垃圾收集器和性能調(diào)優(yōu)參數(shù)讓開(kāi)發(fā)者花費(fèi)大量的時(shí)間在無(wú)休無(wú)盡的性能調(diào)優(yōu)方面。這使有些人因此得出結(jié)論:垃圾收集是糟糕的,通過(guò)調(diào)優(yōu)使垃圾收集較少發(fā)生或者持續(xù)時(shí)間較短是一個(gè)很好的變通辦法,不過(guò)這樣做是有風(fēng)險(xiǎn)的。

考慮一下針對(duì)具體應(yīng)用程序的調(diào)優(yōu),多數(shù)的調(diào)優(yōu)參數(shù)(例如內(nèi)存分配率、對(duì)象大小、響應(yīng)時(shí)間)都是基于當(dāng)前測(cè)試的數(shù)據(jù)量對(duì)應(yīng)用程序的內(nèi)存分配率(譯者注:或者其他參數(shù))調(diào)整。最終可能造成以下兩個(gè)結(jié)果:

1.在測(cè)試中通過(guò)的用例在生產(chǎn)環(huán)境中失敗。
2.數(shù)據(jù)量的變化或者應(yīng)用程序的變化要求重新調(diào)優(yōu)。

調(diào)優(yōu)是需要反復(fù)的,特別是并發(fā)垃圾收集器可能需要很多調(diào)優(yōu)(尤其在生產(chǎn)環(huán)境中)。需要啟發(fā)式方法來(lái)滿(mǎn)足應(yīng)用程序的需要。為了要滿(mǎn)足最壞的情況,調(diào)優(yōu)的結(jié)果可能是一個(gè)非常死板的配置,這也導(dǎo)致了大量的資源浪費(fèi)。這種調(diào)優(yōu)方法是一種堂吉訶德式的探索。事實(shí)上,你越是優(yōu)化垃圾收集器來(lái)匹配特定的負(fù)載,越是遠(yuǎn)離了Java運(yùn)行時(shí)的動(dòng)態(tài)特性。畢竟有多少應(yīng)用程序的負(fù)載是穩(wěn)定的呢,你所預(yù)期的負(fù)載的可靠性又有多高呢?

那么如果你不將注意力放在調(diào)優(yōu)上,能夠做些什么來(lái)防止內(nèi)存溢出錯(cuò)誤和提高響應(yīng)時(shí)間呢?首要的事情就是找到影響Java應(yīng)用程序性能的主要因素。

碎片化

影響Java應(yīng)用程序性能的因素不是垃圾收集器,而是碎片化以及垃圾收集器如何處理碎片化。所謂碎片化是這樣一種狀態(tài):堆空間中有空閑可用的空間,但并沒(méi)有足夠大的連續(xù)內(nèi)存空間,以至于無(wú)法為新對(duì)象分配內(nèi)存。正如在第一篇中提到的,內(nèi)存碎片要么是堆中殘留的一段空間TLAB,要么是在長(zhǎng)期存活對(duì)象中間被釋放的小對(duì)象所占用的空間。

隨著時(shí)間的推移和應(yīng)用程序的運(yùn)行,這些碎片就會(huì)遍布在堆中。在某些情況下,使用了靜態(tài)化調(diào)優(yōu)的參數(shù)可能會(huì)更糟,因?yàn)檫@些參數(shù)無(wú)法滿(mǎn)足應(yīng)用程序的動(dòng)態(tài)需要。應(yīng)用程序無(wú)法有效利用這些碎片化的空間。如果不做任何事情,那么將導(dǎo)致接連不斷的垃圾收集,垃圾收集器嘗試釋放內(nèi)存分配給新對(duì)象。在最壞的情況下,即使是接連不斷的垃圾收集也無(wú)法釋放更多的內(nèi)存(碎片太多),然后JVM不得不拋出內(nèi)存溢出的錯(cuò)誤。你可以通過(guò)重啟應(yīng)用程序來(lái)解決碎片化,這樣Java堆就有連續(xù)的內(nèi)存空間可以分配給新對(duì)象。重啟程序?qū)е洛礄C(jī),而且一段時(shí)間后Java堆將再次充滿(mǎn)碎片,不得不再次重啟。

內(nèi)存溢出錯(cuò)誤會(huì)掛起進(jìn)程,日志顯示垃圾收集器正在超負(fù)荷工作,這些都顯示垃圾收集正試圖釋放內(nèi)存,也表明堆中碎片很多。一些程序員會(huì)試圖通過(guò)再次優(yōu)化垃圾收集器來(lái)解決碎片化問(wèn)題。但我認(rèn)為應(yīng)該尋找更有新意的辦法解決這個(gè)問(wèn)題。接下來(lái)的部分將重點(diǎn)討論解決碎片化的兩個(gè)辦法:分代垃圾收集和壓縮。

分代垃圾收集

你可能聽(tīng)過(guò)這樣的理論:在生產(chǎn)環(huán)境中絕大多數(shù)對(duì)象的存活時(shí)間都很短。分代垃圾收集正是由這一理論衍生出的一種垃圾收集策略。在分代垃圾收集中,我們將堆分為不同的空間(或者叫做代),每個(gè)空間中保存著不同年齡的對(duì)象,所謂對(duì)象的年齡就是對(duì)象存活的垃圾收集周期數(shù)(也就是該對(duì)象多少個(gè)垃圾收集周期后仍然被引用)。

當(dāng)新生代沒(méi)有剩余空間可分配時(shí),新生代的活動(dòng)對(duì)象會(huì)被移動(dòng)到老年代中(通常只有兩個(gè)代。譯者注:只有滿(mǎn)足一定年齡的對(duì)象才會(huì)被移動(dòng)到老年代),分代垃圾收集常常使用單向的復(fù)制收集器,一些更現(xiàn)代的JVM新生代中使用的是并行收集器,當(dāng)然也可以為新生代和老年代分別實(shí)現(xiàn)不同的垃圾收集算法。如果你使用并行收集器或復(fù)制收集器,那么你的新生代收集器就是一個(gè)stop-the-world的收集器(參見(jiàn)之前的解釋?zhuān)?/p>

老年代分配給那些從新生代移出的對(duì)象,這些對(duì)象要么是被引用很長(zhǎng)一段時(shí)間,要么是被一些新生代中對(duì)象集合所引用。偶爾也有大對(duì)象直接被分配到了老年代,因?yàn)橐苿?dòng)大對(duì)象的成本相對(duì)較高。

分代垃圾收集技術(shù)

在分代垃圾收集中,老年代運(yùn)行垃圾收集的頻率較低,而在新生代運(yùn)行垃圾收集的頻率較高,而我們也希望在新生代中垃圾收集周期更短。在極少的情況下,新生代的垃圾收集可能會(huì)比老年代的垃圾收集更頻繁。如果你將新生代設(shè)置的太大時(shí)并且應(yīng)用程序中的多數(shù)對(duì)象都存活較長(zhǎng)時(shí)間,這種情況就可能會(huì)發(fā)生。在這種情況下,如果老年代設(shè)置的太小以至于無(wú)法容納所有的長(zhǎng)時(shí)間存活的對(duì)象,老年代的垃圾收集也會(huì)掙扎于釋放空間給那些被移動(dòng)進(jìn)來(lái)的對(duì)象。不過(guò)通常來(lái)說(shuō)分代垃圾收集可以使應(yīng)用程序獲得更好的性能。

劃分出新生代的另一個(gè)好處是某種程度上解決了碎片化問(wèn)題,或者說(shuō)將最壞的情況推遲了。那些存活時(shí)間短的小對(duì)象本來(lái)可能產(chǎn)生碎片化問(wèn)題,但都在新生代的垃圾收集中被清理了。由于存活時(shí)間長(zhǎng)的對(duì)象被移到老年代時(shí)被更緊湊的分配空間,老年代也更加緊湊了。隨著時(shí)間推移(如果你的應(yīng)用運(yùn)行時(shí)間足夠長(zhǎng)),老年代也會(huì)產(chǎn)生碎片化,這時(shí)需要運(yùn)行一次或是幾次完全垃圾收集,同時(shí)JVM也有可能拋出內(nèi)存溢出錯(cuò)誤。但是劃分出新生代推遲了出現(xiàn)最壞情況的時(shí)間,這對(duì)于很多應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠了。對(duì)于多數(shù)應(yīng)用程序而言,它的確降低了stop-the-world垃圾收集的頻率和內(nèi)存溢出錯(cuò)誤的機(jī)會(huì)。

優(yōu)化分代垃圾收集

正如之前提到的,使用分代垃圾收集帶來(lái)了重復(fù)的調(diào)優(yōu)工作,例如調(diào)整新生代大小、提升率等。我無(wú)法針對(duì)具體應(yīng)用運(yùn)行時(shí)來(lái)強(qiáng)調(diào)怎樣做取舍:選擇固定的大小固然可以?xún)?yōu)化應(yīng)用程序,但同時(shí)也減少了垃圾收集器應(yīng)對(duì)動(dòng)態(tài)變化的能力,而變化是不可避免的。

對(duì)于新生代首要原則就是在確保stop-the-world垃圾收集期間延遲時(shí)間前提下盡可能的加大,同時(shí)也要為那些長(zhǎng)期存活的對(duì)象在堆中保留足夠大的空間。下面是在調(diào)整分代垃圾收集器時(shí)要考慮的一些額外因素:

1.新生代中多數(shù)都是stop-the-world垃圾收集器,新生代設(shè)置的越大,相應(yīng)的暫停時(shí)間就越長(zhǎng)。因此對(duì)于那些受垃圾收集暫停時(shí)間影響大的應(yīng)用程序來(lái)說(shuō),要仔細(xì)考慮將新生代設(shè)置為多大合適。

2.可以在不同的代上使用不同的垃圾收集算法。例如在新生代中使用并行垃圾收集,在老年代中使用并發(fā)垃圾收集。

3.當(dāng)發(fā)現(xiàn)頻繁的提升(譯者注:從新生代移動(dòng)到老年代)失敗時(shí)說(shuō)明老年代中碎片太多了,也就是說(shuō)老年代中沒(méi)有足夠的空間來(lái)存放從新生代移出的對(duì)象。這時(shí)你可以調(diào)整一下提升率(即調(diào)整提升的年齡),或者確保老年代中的垃圾收集算法會(huì)進(jìn)行壓縮(將在下一段討論)并調(diào)整壓縮以適應(yīng)應(yīng)用程序的負(fù)載。也可以增加堆大小和各個(gè)代大小,但是這樣更會(huì)進(jìn)一步延長(zhǎng)老年代上的暫停時(shí)間。要知道碎片化是無(wú)法避免的。

4.分代垃圾收集最適合這樣的應(yīng)用程序,他們有很多存活時(shí)間很短的小對(duì)象,很多對(duì)象在第一輪垃圾收集周期就被回收了。對(duì)于這種應(yīng)用程序分代垃圾收集可以很好的減少碎片化,并將碎片化產(chǎn)生影響的時(shí)機(jī)推遲。

壓縮

盡管分代垃圾收集延遲了出現(xiàn)碎片化和內(nèi)存溢出錯(cuò)誤的時(shí)間,然而壓縮才是真正解決碎片化問(wèn)題的唯一辦法。壓縮是指通過(guò)移動(dòng)對(duì)象來(lái)釋放連續(xù)內(nèi)存塊的垃圾收集策略,這樣通過(guò)壓縮為創(chuàng)建新對(duì)象釋放了足夠大的空間。

移動(dòng)對(duì)象并更新對(duì)象引用是stop-the-world操作,會(huì)帶來(lái)一定的消耗(有一種情況例外,將在本系列的下一篇中討論)。存活的對(duì)象越多,壓縮造成的暫停時(shí)間就越長(zhǎng)。在剩余空間很少并且碎片化嚴(yán)重的情況下(這通常是因?yàn)槌绦蜻\(yùn)行了很長(zhǎng)的時(shí)間),壓縮存活對(duì)象較多的區(qū)域可能會(huì)有幾秒種的暫停時(shí)間,而當(dāng)接近內(nèi)存溢出時(shí),壓縮整個(gè)堆甚至?xí)ㄉ蠋资氲臅r(shí)間。

壓縮的暫停時(shí)間取決于需要移動(dòng)的內(nèi)存大小和需要更新的引用數(shù)量。統(tǒng)計(jì)分析表明堆越大,需要移動(dòng)的活動(dòng)對(duì)象和更新的引用數(shù)量就越多。每移動(dòng)1GB到2GB活動(dòng)對(duì)象的暫停時(shí)間大約是1秒鐘,對(duì)于4GB大小的堆很可能有25%的活動(dòng)對(duì)象,因此偶爾會(huì)有大約1秒的暫停。

壓縮和應(yīng)用程序內(nèi)存墻

應(yīng)用程序內(nèi)存墻是指在垃圾收集產(chǎn)生的暫停(例如:壓縮)前可以設(shè)置的堆大小。根據(jù)系統(tǒng)和應(yīng)用的不同,大多數(shù)的Java應(yīng)用程序內(nèi)存墻都在4GB到20GB之間。這也是多數(shù)的企業(yè)應(yīng)用都是部署在多個(gè)較小的JVM上,而不是少數(shù)較大的JVM上的原因。讓我們考慮一下這個(gè)問(wèn)題:有多少現(xiàn)代企業(yè)的Java應(yīng)用程序設(shè)計(jì)、部署是根據(jù)JVM的壓縮限制來(lái)定義的。在這種情況下,為了繞過(guò)整理堆碎片的暫停時(shí)間,我們接受了更耗費(fèi)管理成本的多個(gè)實(shí)例部署方案??紤]到現(xiàn)在硬件的大容量存儲(chǔ)能力和企業(yè)級(jí)Java應(yīng)用對(duì)增加內(nèi)存的需求,這就有點(diǎn)奇怪了。為什么為每個(gè)實(shí)例只設(shè)置了幾個(gè)GB的內(nèi)存。并發(fā)壓縮將會(huì)打破內(nèi)存墻,這也是我下一篇文章的主題。

總結(jié)

本文是一篇關(guān)于垃圾收集的介紹性文章,幫助你了解有關(guān)垃圾收集的概念和機(jī)制,并希望能夠促使你進(jìn)一步閱讀相關(guān)文章。這里討論的很多東西都已經(jīng)存在了很久,在下一篇中將介紹一些新的概念。例如并發(fā)壓縮,目前是由Azul‘s Zing JVM實(shí)現(xiàn)的。它是一項(xiàng)新興的垃圾收集技術(shù),甚至嘗試重新定義Java內(nèi)存模型,特別是在今天內(nèi)存和處理能力都不斷提高的情況下。

以下是我總結(jié)出的一些關(guān)于垃圾收集的要點(diǎn):

1.不同的垃圾收集算法和實(shí)現(xiàn)適應(yīng)不同的應(yīng)用程序需要,跟蹤垃圾收集器是商業(yè)Java虛擬機(jī)中使用的最多的垃圾收集器。

2.并行垃圾收集在執(zhí)行垃圾收集時(shí)并行使用所有資源。它通常是一個(gè)stop-the-world垃圾收集器,因此有更高的吞吐量,但是應(yīng)用程序的工作線(xiàn)程必須等待垃圾收集線(xiàn)程完成,這對(duì)應(yīng)用程序的響應(yīng)時(shí)間有一定影響。

3.并發(fā)垃圾收集在執(zhí)行收集時(shí),應(yīng)用程序工作線(xiàn)程仍然在運(yùn)行。并發(fā)垃圾收集器需要在應(yīng)用程序需要內(nèi)存之前完成垃圾收集。

4.分代垃圾收集有助于延遲碎片化,但無(wú)法消除碎片化。分代垃圾收集將堆分為兩個(gè)空間,其中一個(gè)空間存放新對(duì)象,另一個(gè)空間存放老對(duì)象。分代垃圾收集適合有很多存活時(shí)間很短的小對(duì)象的應(yīng)用程序。

5.壓縮是解決碎片化的唯一方法。多數(shù)的垃圾收集器都是以stop-the-world的方式執(zhí)行壓縮的,程序運(yùn)行時(shí)間越久,對(duì)象引用越是復(fù)雜,對(duì)象的大小越是分布不均勻都將導(dǎo)致壓縮時(shí)間延長(zhǎng)。堆的大小也會(huì)影響壓縮時(shí)間,因?yàn)榭赡苡懈嗟幕顒?dòng)對(duì)象和引用需要更新。

6.調(diào)優(yōu)有助于延遲內(nèi)存溢出錯(cuò)誤。但是過(guò)度調(diào)優(yōu)的結(jié)果是僵化的配置。在通過(guò)試錯(cuò)的方式開(kāi)始調(diào)優(yōu)之前,要確保清楚生產(chǎn)環(huán)境的負(fù)載、應(yīng)用程序的對(duì)象類(lèi)型以及對(duì)象引用的特性。過(guò)于僵化的配置很可能無(wú)法應(yīng)付動(dòng)態(tài)負(fù)載,因此在設(shè)置非動(dòng)態(tài)值時(shí)一定要了解這樣做的后果。

本系列的下一篇是:深入探討C4(Concurrent Continuously Compacting Collector)垃圾收集算法,敬請(qǐng)期待!

(全文完)

相關(guān)文章

  • ThreadLocal的內(nèi)存泄露問(wèn)題

    ThreadLocal的內(nèi)存泄露問(wèn)題

    這篇文章主要介紹了Java中ThreadLocal的內(nèi)存泄露問(wèn)題,以及為什么會(huì)出現(xiàn)內(nèi)存泄漏,感興趣的小伙伴可以參考閱讀
    2023-03-03
  • MyBatis Plus 將查詢(xún)結(jié)果封裝到指定實(shí)體的方法步驟

    MyBatis Plus 將查詢(xún)結(jié)果封裝到指定實(shí)體的方法步驟

    這篇文章主要介紹了MyBatis Plus 將查詢(xún)結(jié)果封裝到指定實(shí)體的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • java二叉樹(shù)的數(shù)據(jù)插入算法介紹

    java二叉樹(shù)的數(shù)據(jù)插入算法介紹

    大家好,本篇文章主要講的是java二叉樹(shù)的數(shù)據(jù)插入算法介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽
    2021-12-12
  • JavaTCP上傳文本文件代碼

    JavaTCP上傳文本文件代碼

    今天小編就為大家分享一篇關(guān)于JavaTCP上傳文本文件代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-02-02
  • 設(shè)置tomcat啟用gzip壓縮的具體操作方法

    設(shè)置tomcat啟用gzip壓縮的具體操作方法

    如果發(fā)現(xiàn)內(nèi)容沒(méi)有被壓縮,可以考慮調(diào)整compressionMinSize大小,如果請(qǐng)求資源小于這個(gè)數(shù)值,則不會(huì)啟用壓縮
    2013-08-08
  • java單例模式使用及注意事項(xiàng)

    java單例模式使用及注意事項(xiàng)

    這篇文章主要介紹了java單例模式使用及注意事項(xiàng),需要的朋友可以參考下
    2014-04-04
  • 解決MyBatis中Enum字段參數(shù)解析問(wèn)題

    解決MyBatis中Enum字段參數(shù)解析問(wèn)題

    本文主要介紹了解決MyBatis中Enum字段參數(shù)解析問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • WIN7系統(tǒng)JavaEE(java)環(huán)境配置教程(一)

    WIN7系統(tǒng)JavaEE(java)環(huán)境配置教程(一)

    這篇文章主要介紹了WIN7系統(tǒng)JavaEE(java+tomcat7+Eclipse)環(huán)境配置教程,本文重點(diǎn)在于java配置,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Java BeanUtils工具類(lèi)常用方法講解

    Java BeanUtils工具類(lèi)常用方法講解

    這篇文章主要介紹了Java BeanUtils工具類(lèi)常用方法講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java解決約瑟夫問(wèn)題代碼實(shí)例

    Java解決約瑟夫問(wèn)題代碼實(shí)例

    這篇文章主要介紹了Java解決約瑟夫(環(huán))問(wèn)題的代碼實(shí)例,決約瑟問(wèn)題貌似經(jīng)常出現(xiàn)在面試題中,需要的朋友可以參考下
    2014-03-03

最新評(píng)論