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

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

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

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

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

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

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

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

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

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

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

兩類垃圾收集

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

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

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

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

跟蹤收集器

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

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

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

跟蹤收集算法

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

復(fù)制垃圾收集器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

并行收集器

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

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

并發(fā)收集器

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

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

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

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

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

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

1.在測試中通過的用例在生產(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ā)式方法來滿足應(yīng)用程序的需要。為了要滿足最壞的情況,調(diào)優(yōu)的結(jié)果可能是一個非常死板的配置,這也導(dǎo)致了大量的資源浪費(fèi)。這種調(diào)優(yōu)方法是一種堂吉訶德式的探索。事實(shí)上,你越是優(yōu)化垃圾收集器來匹配特定的負(fù)載,越是遠(yuǎn)離了Java運(yùn)行時的動態(tài)特性。畢竟有多少應(yīng)用程序的負(fù)載是穩(wěn)定的呢,你所預(yù)期的負(fù)載的可靠性又有多高呢?

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

碎片化

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

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

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

分代垃圾收集

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

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

老年代分配給那些從新生代移出的對象,這些對象要么是被引用很長一段時間,要么是被一些新生代中對象集合所引用。偶爾也有大對象直接被分配到了老年代,因?yàn)橐苿哟髮ο蟮某杀鞠鄬^高。

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

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

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

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

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

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

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

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

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

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

壓縮

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

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

壓縮的暫停時間取決于需要移動的內(nèi)存大小和需要更新的引用數(shù)量。統(tǒng)計(jì)分析表明堆越大,需要移動的活動對象和更新的引用數(shù)量就越多。每移動1GB到2GB活動對象的暫停時間大約是1秒鐘,對于4GB大小的堆很可能有25%的活動對象,因此偶爾會有大約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)用都是部署在多個較小的JVM上,而不是少數(shù)較大的JVM上的原因。讓我們考慮一下這個問題:有多少現(xiàn)代企業(yè)的Java應(yīng)用程序設(shè)計(jì)、部署是根據(jù)JVM的壓縮限制來定義的。在這種情況下,為了繞過整理堆碎片的暫停時間,我們接受了更耗費(fèi)管理成本的多個實(shí)例部署方案。考慮到現(xiàn)在硬件的大容量存儲能力和企業(yè)級Java應(yīng)用對增加內(nèi)存的需求,這就有點(diǎn)奇怪了。為什么為每個實(shí)例只設(shè)置了幾個GB的內(nèi)存。并發(fā)壓縮將會打破內(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í)行垃圾收集時并行使用所有資源。它通常是一個stop-the-world垃圾收集器,因此有更高的吞吐量,但是應(yīng)用程序的工作線程必須等待垃圾收集線程完成,這對應(yīng)用程序的響應(yīng)時間有一定影響。

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

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

5.壓縮是解決碎片化的唯一方法。多數(shù)的垃圾收集器都是以stop-the-world的方式執(zhí)行壓縮的,程序運(yùn)行時間越久,對象引用越是復(fù)雜,對象的大小越是分布不均勻都將導(dǎo)致壓縮時間延長。堆的大小也會影響壓縮時間,因?yàn)榭赡苡懈嗟幕顒訉ο蠛鸵眯枰隆?/p>

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

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

(全文完)

相關(guān)文章

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

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

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

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

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

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

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

    JavaTCP上傳文本文件代碼

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

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

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

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

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

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

    本文主要介紹了解決MyBatis中Enum字段參數(shù)解析問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    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工具類常用方法講解

    Java BeanUtils工具類常用方法講解

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

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

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

最新評論