摘要:此外,從結(jié)果我們可以得知,一個(gè)堆對象的放在局部變量表中的第一項(xiàng)引用會永遠(yuǎn)存在,在方法體內(nèi)可以將引用賦值給其他變量,這樣堆中對象就可以被其他變量所引用,即不會被回收。
原創(chuàng)不易,如需轉(zhuǎn)載,請注明出處https://www.cnblogs.com/baixianlong/p/10697554.html,多多支持哈!
一、什么是GC?GC是垃圾收集的意思,內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測對象是否超過作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語言沒有提供釋放已分配內(nèi)存的顯示操作方法。Java程序員不用擔(dān)心內(nèi)存管理,因?yàn)槔占鲿詣?dòng)進(jìn)行管理。要請求垃圾收集,可以調(diào)用下面的方法之一:System.gc() 或Runtime.getRuntime().gc()。
二、哪些內(nèi)存需要回收?哪些內(nèi)存需要回收是垃圾回收機(jī)制第一個(gè)要考慮的問題,所謂“要回收的垃圾”無非就是那些不可能再被任何途徑使用的對象。那么如何找到這些對象?
引用計(jì)數(shù)法:這種算法不能解決對象之間相互引用的情況,所以這種方法不靠譜
可達(dá)性分析法:這個(gè)算法的基本思想是通過一系列稱為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達(dá))時(shí),則證明此對象是不可用的。
那么問題又來了,如何選取GCRoots對象呢?在Java語言中,可以作為GCRoots的對象包括下面幾種:
虛擬機(jī)棧(棧幀中的局部變量區(qū),也叫做局部變量表)中引用的對象。
方法區(qū)中的類靜態(tài)屬性引用的對象。
方法區(qū)中常量引用的對象。
本地方法棧中JNI(Native方法)引用的對象。
下面給出一個(gè)GCRoots的例子,如下圖,為GCRoots的引用鏈,obj8、obj9、obj10都沒有到GCRoots對象的引用鏈,所以會進(jìn)行回收。
三、四種引用狀以及基于可達(dá)性分析的內(nèi)存回收原理對于可達(dá)性分析算法而言,未到達(dá)的對象并非是“非死不可”的,若要宣判一個(gè)對象死亡,至少需要經(jīng)歷兩次標(biāo)記階段。
如果對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GCRoots相連的引用鏈,則該對象被第一次標(biāo)記并進(jìn)行一次篩選,篩選條件為是否有必要執(zhí)行該對象的finalize方法,若對象沒有覆蓋finalize方法或者該finalize方法是否已經(jīng)被虛擬機(jī)執(zhí)行過了,則均視作不必要執(zhí)行該對象的finalize方法,即該對象將會被回收。反之,若對象覆蓋了finalize方法并且該finalize方法并沒有被執(zhí)行過,那么,這個(gè)對象會被放置在一個(gè)叫F-Queue的隊(duì)列中,之后會由虛擬機(jī)自動(dòng)建立的、優(yōu)先級低的Finalizer線程去執(zhí)行,而虛擬機(jī)不必要等待該線程執(zhí)行結(jié)束,即虛擬機(jī)只負(fù)責(zé)建立線程,其他的事情交給此線程去處理。
對F-Queue中對象進(jìn)行第二次標(biāo)記,如果對象在finalize方法中拯救了自己,即關(guān)聯(lián)上了GCRoots引用鏈,如把this關(guān)鍵字賦值給其他變量,那么在第二次標(biāo)記的時(shí)候該對象將從“即將回收”的集合中移除,如果對象還是沒有拯救自己,那就會被回收。如下代碼演示了一個(gè)對象如何在finalize方法中拯救了自己,然而,它只能拯救自己一次,第二次就被回收了。具體代碼如下:
public class GC {
public static GC SAVE_HOOK = null; public static void main(String[] args) throws InterruptedException { // 新建對象,因?yàn)镾AVE_HOOK指向這個(gè)對象,對象此時(shí)的狀態(tài)是(reachable,unfinalized) SAVE_HOOK = new GC(); //將SAVE_HOOK設(shè)置成null,此時(shí)剛才創(chuàng)建的對象就不可達(dá)了,因?yàn)闆]有句柄再指向它了,對象此時(shí)狀態(tài)是(unreachable,unfinalized) SAVE_HOOK = null; //強(qiáng)制系統(tǒng)執(zhí)行垃圾回收,系統(tǒng)發(fā)現(xiàn)剛才創(chuàng)建的對象處于unreachable狀態(tài),并檢測到這個(gè)對象的類覆蓋了finalize方法,因此把這個(gè)對象放入F-Queue隊(duì)列,由低優(yōu)先級線程執(zhí)行它的finalize方法,此時(shí)對象的狀態(tài)變成(unreachable, finalizable)或者是(finalizer-reachable,finalizable) System.gc(); // sleep,目的是給低優(yōu)先級線程從F-Queue隊(duì)列取出對象并執(zhí)行其finalize方法提供機(jī)會。在執(zhí)行完對象的finalize方法中的super.finalize()時(shí),對象的狀態(tài)變成(unreachable,finalized)狀態(tài),但接下來在finalize方法中又執(zhí)行了SAVE_HOOK = this;這句話,又有句柄指向這個(gè)對象了,對象又可達(dá)了。因此對象的狀態(tài)又變成了(reachable, finalized)狀態(tài)。 Thread.sleep(500); // 這里樓主說對象處于(reachable,finalized)狀態(tài)應(yīng)該是合理的。對象的finalized方法被執(zhí)行了,因此是finalized狀態(tài)。又因?yàn)樵趂inalize方法是執(zhí)行了SAVE_HOOK=this這句話,本來是unreachable的對象,又變成reachable了。 if (null != SAVE_HOOK) { //此時(shí)對象應(yīng)該處于(reachable, finalized)狀態(tài) // 這句話會輸出,注意對象由unreachable,經(jīng)過finalize復(fù)活了。 System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } // 再一次將SAVE_HOOK放空,此時(shí)剛才復(fù)活的對象,狀態(tài)變成(unreachable,finalized) SAVE_HOOK = null; // 再一次強(qiáng)制系統(tǒng)回收垃圾,此時(shí)系統(tǒng)發(fā)現(xiàn)對象不可達(dá),雖然覆蓋了finalize方法,但已經(jīng)執(zhí)行過了,因此直接回收。 System.gc(); // 為系統(tǒng)回收垃圾提供機(jī)會 Thread.sleep(500); if (null != SAVE_HOOK) { // 這句話不會輸出,因?yàn)閷ο笠呀?jīng)徹底消失了。 System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("execute method finalize()"); // 這句話讓對象的狀態(tài)由unreachable變成reachable,就是對象復(fù)活 SAVE_HOOK = this; }
}
運(yùn)行結(jié)果如下:
leesf null finalize method executed! leesf yes, i am still alive :) no, i am dead : (
由結(jié)果可知,該對象拯救了自己一次,第二次沒有拯救成功,因?yàn)閷ο蟮膄inalize方法最多被虛擬機(jī)調(diào)用一次。此外,從結(jié)果我們可以得知,一個(gè)堆對象的this(放在局部變量表中的第一項(xiàng))引用會永遠(yuǎn)存在,在方法體內(nèi)可以將this引用賦值給其他變量,這樣堆中對象就可以被其他變量所引用,即不會被回收。
四、方法區(qū)的垃圾回收1、方法區(qū)的垃圾回收主要回收兩部分內(nèi)容:
廢棄常量
無用的類
2、既然進(jìn)行垃圾回收,就需要判斷哪些是廢棄常量,哪些是無用的類?
如何判斷廢棄常量呢?以字面量回收為例,如果一個(gè)字符串“abc”已經(jīng)進(jìn)入常量池,但是當(dāng)前系統(tǒng)沒有任何一個(gè)String對象引用了叫做“abc”的字面量,那么,如果發(fā)生垃圾回收并且有必要時(shí),“abc”就會被系統(tǒng)移出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。
如何判斷無用的類呢?需要滿足以下三個(gè)條件:
該類的所有實(shí)例都已經(jīng)被回收,即Java堆中不存在該類的任何實(shí)例。
加載該類的ClassLoader已經(jīng)被回收。
該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
五、垃圾收集算法(垃圾回收器都是基于這些算法來實(shí)現(xiàn)) 1、標(biāo)記-清除(Mark-Sweep)算法這是最基礎(chǔ)的算法,標(biāo)記-清除算法就如同它的名字樣,分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對象,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。這種算法的不足主要體現(xiàn)在效率和空間,從效率的角度講,標(biāo)記和清除兩個(gè)過程的效率都不高;從空間的角度講,標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片, 內(nèi)存碎片太多可能會導(dǎo)致以后程序運(yùn)行過程中在需要分配較大對象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)一次垃圾收集動(dòng)作。標(biāo)記-清除算法執(zhí)行過程如圖:
2、復(fù)制(Copying)算法復(fù)制算法是為了解決效率問題而出現(xiàn)的,它將可用的內(nèi)存分為兩塊,每次只用其中一塊,當(dāng)這一塊內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已經(jīng)使用過的內(nèi)存空間一次性清理掉。這樣每次只需要對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也不需要考慮內(nèi)存碎片等復(fù)雜情況,只需要移動(dòng)指針,按照順序分配即可。復(fù)制算法的執(zhí)行過程如圖:
不過這種算法有個(gè)缺點(diǎn),內(nèi)存縮小為了原來的一半,這樣代價(jià)太高了。現(xiàn)在的商用虛擬機(jī)都采用這種算法來回收新生代,不過研究表明1:1的比例非常不科學(xué),因此新生代的內(nèi)存被劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。每次回收時(shí),將Eden和Survivor中還存活著的對象一次性復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden區(qū)和Survivor區(qū)的比例為8:1,意思是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%。當(dāng)然,我們沒有辦法保證每次回收都只有不多于10%的對象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保(Handle Promotion)。
3、標(biāo)記-整理(Mark-Compact)算法復(fù)制算法在對象存活率較高的場景下要進(jìn)行大量的復(fù)制操作,效率很低。萬一對象100%存活,那么需要有額外的空間進(jìn)行分配擔(dān)保。老年代都是不易被回收的對象,對象存活率高,因此一般不能直接選用復(fù)制算法。根據(jù)老年代的特點(diǎn),有人提出了另外一種標(biāo)記-整理算法,過程與標(biāo)記-清除算法一樣,不過不是直接對可回收對象進(jìn)行清理,而是讓所有存活對象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。標(biāo)記-整理算法的工作過程如圖:
六、垃圾收集器垃圾收集器就是上面講的理論知識的具體實(shí)現(xiàn)了。不同虛擬機(jī)所提供的垃圾收集器可能會有很大差別,我們使用的是HotSpot,HotSpot這個(gè)虛擬機(jī)所包含的所有收集器如圖:
上圖展示了7種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,那說明它們可以搭配使用。虛擬機(jī)所處的區(qū)域說明它是屬于新生代收集器還是老年代收集器。多說一句,我們必須明確一個(gè)觀點(diǎn):沒有最好的垃圾收集器,更加沒有萬能的收集器,只能選擇對具體應(yīng)用最合適的收集器。這也是HotSpot為什么要實(shí)現(xiàn)這么多收集器的原因。OK,下面一個(gè)一個(gè)看一下收集器。
Serial收集器最基本、發(fā)展歷史最久的收集器,這個(gè)收集器是一個(gè)采用復(fù)制算法的單線程的收集器,單線程一方面意味著它只會使用一個(gè)CPU或一條線程去完成垃圾收集工作,另一方面也意味著它進(jìn)行垃圾收集時(shí)必須暫停其他線程的所有工作,直到它收集結(jié)束為止。后者意味著,在用戶不可見的情況下要把用戶正常工作的線程全部停掉,這對很多應(yīng)用是難以接受的。不過實(shí)際上到目前為止,Serial收集器依然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器,因?yàn)樗唵味咝АS脩糇烂鎽?yīng)用場景中,分配給虛擬機(jī)管理的內(nèi)存一般來說不會很大,收集幾十兆甚至一兩百兆的新生代停頓時(shí)間在幾十毫秒最多一百毫秒,只要不是頻繁發(fā)生,這點(diǎn)停頓是完全可以接受的。Serial收集器運(yùn)行過程如下圖所示:
說明:1. 需要STW(Stop The World),停頓時(shí)間長。2. 簡單高效,對于單個(gè)CPU環(huán)境而言,Serial收集器由于沒有線程交互開銷,可以獲取最高的單線程收集效率。
ParNew收集器ParNew收集器其實(shí)就是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集外,其余行為和Serial收集器完全一樣,包括使用的也是復(fù)制算法。ParNew收集器除了多線程以外和Serial收集器并沒有太多創(chuàng)新的地方,但是它卻是Server模式下的虛擬機(jī)首選的新生代收集器,其中有一個(gè)很重要的和性能無關(guān)的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作(看圖)。CMS收集器是一款幾乎可以認(rèn)為有劃時(shí)代意義的垃圾收集器,因?yàn)樗谝淮螌?shí)現(xiàn)了讓垃圾收集線程與用戶線程基本上同時(shí)工作。ParNew收集器在單CPU的環(huán)境中絕對不會有比Serial收集器更好的效果,甚至由于線程交互的開銷,該收集器在兩個(gè)CPU的環(huán)境中都不能百分之百保證可以超越Serial收集器。當(dāng)然,隨著可用CPU數(shù)量的增加,它對于GC時(shí)系統(tǒng)資源的有效利用還是很有好處的。它默認(rèn)開啟的收集線程數(shù)與CPU數(shù)量相同,在CPU數(shù)量非常多的情況下,可以使用-XX:ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。ParNew收集器運(yùn)行過程如下圖所示:
Parallel Scavenge收集器Parallel Scavenge收集器也是一個(gè)新生代收集器,也是用復(fù)制算法的收集器,也是并行的多線程收集器,但是它的特點(diǎn)是它的關(guān)注點(diǎn)和其他收集器不同。介紹這個(gè)收集器主要還是介紹吞吐量的概念。CMS等收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是打到一個(gè)可控制的吞吐量。所謂吞吐量的意思就是CPU用于運(yùn)行用戶代碼時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),虛擬機(jī)總運(yùn)行100分鐘,垃圾收集1分鐘,那吞吐量就是99%。另外,Parallel Scavenge收集器是虛擬機(jī)運(yùn)行在Server模式下的默認(rèn)垃圾收集器。
停頓時(shí)間短適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn);高吞吐量則可以高效率利用CPU時(shí)間,盡快完成運(yùn)算任務(wù),主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)。
虛擬機(jī)提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio兩個(gè)參數(shù)來精確控制最大垃圾收集停頓時(shí)間和吞吐量大小。不過不要以為前者越小越好,GC停頓時(shí)間的縮短是以犧牲吞吐量和新生代空間換取的。由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也被稱為“吞吐量優(yōu)先收集器”。Parallel Scavenge收集器有一個(gè)-XX:+UseAdaptiveSizePolicy參數(shù),這是一個(gè)開關(guān)參數(shù),這個(gè)參數(shù)打開之后,就不需要手動(dòng)指定新生代大小、Eden區(qū)和Survivor參數(shù)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況以及性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量。如果對于垃圾收集器運(yùn)作原理不太了解,以至于在優(yōu)化比較困難的時(shí)候,使用Parallel Scavenge收集器配合自適應(yīng)調(diào)節(jié)策略,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成將是一個(gè)不錯(cuò)的選擇。
Serial Old收集器Serial收集器的老年代版本,同樣是一個(gè)單線程收集器,使用“標(biāo)記-整理算法”,這個(gè)收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用。
Parallel Old收集器Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。這個(gè)收集器在JDK 1.6之后的出現(xiàn),“吞吐量優(yōu)先收集器”終于有了比較名副其實(shí)的應(yīng)用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge收集器+Parallel Old收集器的組合。運(yùn)行過程如下圖所示:
CMS收集器CMS(Conrrurent Mark Sweep)收集器是以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。使用標(biāo)記 - 清除算法,收集過程分為如下四步:
初始標(biāo)記,標(biāo)記GCRoots能直接關(guān)聯(lián)到的對象,時(shí)間很短。
并發(fā)標(biāo)記,進(jìn)行GCRoots Tracing(可達(dá)性分析)過程,時(shí)間很長。
重新標(biāo)記,修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄,時(shí)間較長。
并發(fā)清除,回收內(nèi)存空間,時(shí)間很長。
其中,并發(fā)標(biāo)記與并發(fā)清除兩個(gè)階段耗時(shí)最長,但是可以與用戶線程并發(fā)執(zhí)行。運(yùn)行過程如下圖所示:
說明:
對CPU資源非常敏感,可能會導(dǎo)致應(yīng)用程序變慢,吞吐率下降。
無法處理浮動(dòng)垃圾,因?yàn)樵诓l(fā)清理階段用戶線程還在運(yùn)行,自然就會產(chǎn)生新的垃圾,而在此次收集中無法收集他們,只能留到下次收集,這部分垃圾為浮動(dòng)垃圾,同時(shí),由于用戶線程并發(fā)執(zhí)行,所以需要預(yù)留一部分老年代空間提供并發(fā)收集時(shí)程序運(yùn)行使用。
由于采用的標(biāo)記 - 清除算法,會產(chǎn)生大量的內(nèi)存碎片,不利于大對象的分配,可能會提前觸發(fā)一次Full GC。虛擬機(jī)提供了-XX:+UseCMSCompactAtFullCollection參數(shù)來進(jìn)行碎片的合并整理過程,這樣會使得停頓時(shí)間變長,虛擬機(jī)還提供了一個(gè)參數(shù)配置,-XX:+CMSFullGCsBeforeCompaction,用于設(shè)置執(zhí)行多少次不壓縮的Full GC后,接著來一次帶壓縮的GC。
G1收集器G1算法將堆劃分為若干個(gè)區(qū)域(Region),它仍然屬于分代收集器。不過,這些區(qū)域的一部分包含新生代,新生代的垃圾收集依然采用暫停所有應(yīng)用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區(qū)域,G1收集器通過將對象從一個(gè)區(qū)域復(fù)制到另外一個(gè)區(qū)域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內(nèi)存碎片問題的存在了。
在G1中,還有一種特殊的區(qū)域,叫Humongous區(qū)域。 如果一個(gè)對象占用的空間超過了分區(qū)容量50%以上,G1收集器就認(rèn)為這是一個(gè)巨型對象。這些巨型對象,默認(rèn)直接會被分配在年老代,但是如果它是一個(gè)短期存在的巨型對象,就會對垃圾收集器造成負(fù)面影響。為了解決這個(gè)問題,G1劃分了一個(gè)Humongous區(qū),它用來專門存放巨型對象。如果一個(gè)H區(qū)裝不下一個(gè)巨型對象,那么G1會尋找連續(xù)的H分區(qū)來存儲。為了能找到連續(xù)的H區(qū),有時(shí)候不得不啟動(dòng)Full GC。
G1主要有以下特點(diǎn):
并行和并發(fā)。使用多個(gè)CPU來縮短Stop The World停頓時(shí)間,與用戶線程并發(fā)執(zhí)行。
分代收集。獨(dú)立管理整個(gè)堆,但是能夠采用不同的方式去處理新創(chuàng)建對象和已經(jīng)存活了一段時(shí)間、熬過多次GC的舊對象,以獲取更好的收集效果。
空間整合。基于標(biāo)記 - 整理算法,無內(nèi)存碎片產(chǎn)生。
可預(yù)測的停頓。能簡歷可預(yù)測的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒。
在G1之前的垃圾收集器,收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣。使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分(可以不連續(xù))Region的集合。
七、CMS和G1對比(過去 vs 未來) CMS垃圾回收器 CMS堆內(nèi)存結(jié)構(gòu)劃分:新生代:eden space + 2個(gè)survivor
老年代:old space
持久代:1.8之前的perm space
元空間:1.8之后的metaspace
注意:這些space必須是地址連續(xù)的空間
CMS中垃圾回收模式
對象分配
優(yōu)先在Eden區(qū)分配
在JVM內(nèi)存模型一文中, 我們大致了解了VM年輕代堆內(nèi)存可以劃分為一塊Eden區(qū)和兩塊Survivor區(qū). 在大多數(shù)情況下, 對象在新生代Eden區(qū)中分配, 當(dāng)Eden區(qū)沒有足夠空間分配時(shí), VM發(fā)起一次Minor GC, 將Eden區(qū)和其中一塊Survivor區(qū)內(nèi)尚存活的對象放入另一塊Survivor區(qū)域, 如果在Minor GC期間發(fā)現(xiàn)新生代存活對象無法放入空閑的Survivor區(qū), 則會通過空間分配擔(dān)保機(jī)制使對象提前進(jìn)入老年代(空間分配擔(dān)保見下).
大對象直接進(jìn)入老年代
Serial和ParNew兩款收集器提供了-XX:PretenureSizeThreshold的參數(shù), 令大于該值的大對象直接在老年代分配, 這樣做的目的是避免在Eden區(qū)和Survivor區(qū)之間產(chǎn)生大量的內(nèi)存復(fù)制(大對象一般指 需要大量連續(xù)內(nèi)存的Java對象, 如很長的字符串和數(shù)組), 因此大對象容易導(dǎo)致還有不少空閑內(nèi)存就提前觸發(fā)GC以獲取足夠的連續(xù)空間. 然而取歷次晉升的對象的平均大小也是有一定風(fēng)險(xiǎn)的, 如果某次Minor GC存活后的對象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無法存放這些對象了), 此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).
空間分配擔(dān)保
在執(zhí)行Minor GC前, VM會首先檢查老年代是否有足夠的空間存放新生代尚存活對象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個(gè)Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況時(shí), 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無法容納的對象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對象. 但存活對象的大小在實(shí)際完成GC前是無法明確知道的, 因此Minor GC前, VM會先首先檢查老年代連續(xù)空間是否大于新生代對象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間).
對象晉升
年齡閾值
VM為每個(gè)對象定義了一個(gè)對象年齡(Age)計(jì)數(shù)器, 對象在Eden出生如果經(jīng)第一次Minor GC后仍然存活, 且能被Survivor容納的話, 將被移動(dòng)到Survivor空間中, 并將年齡設(shè)為1. 以后對象在Survivor區(qū)中每熬過一次Minor GC年齡就+1. 當(dāng)增加到一定程度(-XX:MaxTenuringThreshold, 默認(rèn)15), 將會晉升到老年代.
提前晉升: 動(dòng)態(tài)年齡判定
然而VM并不總是要求對象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代: 如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半, 年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代, 而無須等到晉升年齡.G1垃圾回收器 G1堆內(nèi)存結(jié)構(gòu)劃分(它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region) G1中提供了三種垃圾回收模式:young gc、mixed gc 和 full gc
Young GC
發(fā)生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內(nèi)存,當(dāng)所有eden region被耗盡無法申請內(nèi)存時(shí),就會觸發(fā)一次young gc,這種觸發(fā)機(jī)制和之前的young gc差不多,執(zhí)行完一次young gc,活躍對象會被拷貝到survivor region或者晉升到old region中,空閑的region會被放入空閑列表中,等待下次被使用。
Mixed GC
當(dāng)越來越多的對象晉升到老年代old region時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會觸發(fā)一個(gè)混合的垃圾收集器,即mixed gc,該算法并不是一個(gè)old gc,除了回收整個(gè)young region,還會回收一部分的old region,這里需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進(jìn)行收集,從而可以對垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。
Full GC
如果對象內(nèi)存分配速度過快,mixed gc來不及回收,導(dǎo)致老年代被填滿,就會觸發(fā)一次full gc,G1的full gc算法就是單線程執(zhí)行的serial old gc,會導(dǎo)致異常長時(shí)間的暫停時(shí)間,需要進(jìn)行不斷的調(diào)優(yōu),盡可能的避免full gc.
八、各種垃圾收集器的選用
首先查看你使用的垃圾回收器是什么?
java -XX:+PrintCommandLineFlags -version
根據(jù)自身系統(tǒng)需求選擇最合適的垃圾回收器(沒有最好的,只有最是適合的)
九、總結(jié)到此GC的內(nèi)存就差不多了,其中不免有些錯(cuò)誤的地方,或者理解有偏頗的地方歡迎大家提出來!
關(guān)于GC更細(xì)粒度的調(diào)優(yōu),沒敢妄言,今后有了實(shí)戰(zhàn)事例在補(bǔ)上!!!
個(gè)人博客地址:
csdn:https://blog.csdn.net/tiantuo6513cnblogs:https://www.cnblogs.com/baixianlong
segmentfault:https://segmentfault.com/u/baixianlong
github:https://github.com/xianlongbai
本文參考:
https://www.cnblogs.com/xiaox...;/font>
https://zhuanlan.zhihu.com/p/...;/font>
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/77579.html
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:在之前,它是一個(gè)備受爭議的關(guān)鍵字,因?yàn)樵诔绦蛑惺褂盟占骼斫夂驮矸治龊喎Q,是后提供的面向大內(nèi)存區(qū)數(shù)到數(shù)多核系統(tǒng)的收集器,能夠?qū)崿F(xiàn)軟停頓目標(biāo)收集并且具有高吞吐量具有更可預(yù)測的停頓時(shí)間。 35 個(gè) Java 代碼性能優(yōu)化總結(jié) 優(yōu)化代碼可以減小代碼的體積,提高代碼運(yùn)行的效率。 從 JVM 內(nèi)存模型談線程安全 小白哥帶你打通任督二脈 Java使用讀寫鎖替代同步鎖 應(yīng)用情景 前一陣有個(gè)做...
閱讀 1483·2023-04-25 15:40
閱讀 2834·2021-08-11 11:15
閱讀 2273·2019-08-26 13:48
閱讀 2844·2019-08-26 12:18
閱讀 2448·2019-08-23 18:23
閱讀 2905·2019-08-23 17:01
閱讀 2978·2019-08-23 16:29
閱讀 1101·2019-08-23 15:15