這篇文章主要介紹“JVM垃圾回收基本原理是什么”,在日常操作中,相信很多人在JVM垃圾回收基本原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JVM垃圾回收基本原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)是一家專業(yè)提供瓊中黎族企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站制作、網(wǎng)站建設(shè)、H5場景定制、小程序制作等業(yè)務(wù)。10年已為瓊中黎族眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
Java的內(nèi)存分配與回收全部由JVM垃圾回收進(jìn)程自動完成。與C語言不同,Java開發(fā)者不需要自己編寫代碼實現(xiàn)垃圾回收。這是Java深受大家歡迎的眾多特性之一,能夠幫助程序員更好地編寫Java程序。
JavaAPI:一系列幫助開發(fā)者創(chuàng)建Java應(yīng)用程序的封裝好的庫。
Java 開發(fā)工具包 (JDK):一系列工具幫助開發(fā)者創(chuàng)建Java應(yīng)用程序。JDK包含工具編譯、運行、打包、分發(fā)和監(jiān)視Java應(yīng)用程序。
Java 虛擬機(jī)(JVM):JVM是一個抽象的計算機(jī)結(jié)構(gòu)。Java程序根據(jù)JVM的特性編寫。JVM針對特定于操作系統(tǒng)并且可以將Java指令翻譯成底層系統(tǒng)的指令并執(zhí)行。JVM確保了Java的平臺無關(guān)性。
Java 運行環(huán)境(JRE):JRE包含JVM實現(xiàn)和Java API。
每種JVM實現(xiàn)可能采用不同的方法實現(xiàn)垃圾回收機(jī)制。在收購SUN之前,Oracle使用的是JRockit JVM,收購之后使用HotSpot JVM。目前Oracle擁有兩種JVM實現(xiàn)并且一段時間后兩個JVM實現(xiàn)會合二為一。
HotSpot JVM是目前Oracle SE平臺標(biāo)準(zhǔn)核心組件的一部分。在這篇垃圾回收教程中,我們將會了解基于HotSpot虛擬機(jī)的垃圾回收原則。
我們有必要了解堆內(nèi)存在JVM內(nèi)存模型的角色。在運行時,Java的實例被存放在堆內(nèi)存區(qū)域。當(dāng)一個對象不再被引用時,滿足條件就會從堆內(nèi)存移除。在垃圾回收進(jìn)程中,這些對象將會從堆內(nèi)存移除并且內(nèi)存空間被回收。堆內(nèi)存以下三個主要區(qū)域:
新生代(Young Generation)
Eden空間(Eden space,任何實例都通過Eden空間進(jìn)入運行時內(nèi)存區(qū)域)
S0 Survivor空間(S0 Survivor space,存在時間長的實例將會從Eden空間移動到S0 Survivor空間)
S1 Survivor空間 (存在時間更長的實例將會從S0 Survivor空間移動到S1 Survivor空間)
老年代(Old Generation)實例將從S1提升到Tenured(終身代)
永久代(Permanent Generation)包含類、方法等細(xì)節(jié)的元信息
永久代空間 在Java SE8特性中已經(jīng)被移除。
Java 垃圾回收是一項自動化的過程,用來管理程序所使用的運行時內(nèi)存。通過這一自動化過程,JVM 解除了程序員在程序中分配和釋放內(nèi)存資源的開銷。
作為一個自動的過程,程序員不需要在代碼中顯示地啟動垃圾回收過程。System.gc()
和Runtime.gc()
用來請求JVM啟動垃圾回收。
雖然這個請求機(jī)制提供給程序員一個啟動 GC 過程的機(jī)會,但是啟動由 JVM負(fù)責(zé)。JVM可以拒絕這個請求,所以并不保證這些調(diào)用都將執(zhí)行垃圾回收。啟動時機(jī)的選擇由JVM決定,并且取決于堆內(nèi)存中Eden區(qū)是否可用。JVM將這個選擇留給了Java規(guī)范的實現(xiàn),不同實現(xiàn)具體使用的算法不盡相同。
毋庸置疑,我們知道垃圾回收過程是不能被強(qiáng)制執(zhí)行的。我剛剛發(fā)現(xiàn)了一個調(diào)用System.gc()
有意義的場景。通過這篇文章了解一下
適合調(diào)用System.gc() 這種極端情況。
說到GC類型,就更有意思了,為什么呢,因為業(yè)界沒有統(tǒng)一的嚴(yán)格意義上的界限,也沒有嚴(yán)格意義上的GC類型,都是左邊一個教授一套名字,右邊一個作者一套名字。為什么會有這個情況呢,因為GC類型是和收集器有關(guān)的,不同的收集器會有自己獨特的一些收集類型。所以作者在這里引用R大關(guān)于GC類型的介紹,作者覺得還是比較妥當(dāng)準(zhǔn)確的。如下:
Partial GC:并不收集整個GC堆的模式
Young GC(Minor GC):只收集young gen的GC
Old GC:只收集old gen的GC。只有CMS的concurrent collection是這個模式
Mixed GC:收集整個young gen以及部分old gen的GC。只有G1有這個模式
Full GC(Major GC):收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。
上面大家也看到了,GC類型分分類是和收集器有關(guān)的,那么當(dāng)然了,對于不同的收集器,GC觸發(fā)時機(jī)也是不一樣的,作者就針對默認(rèn)的serial GC來說:
young GC:當(dāng)young gen中的eden區(qū)分配滿的時候觸發(fā)。注意young GC中有部分存活對象會晉升到old gen,所以young GC后old gen的占用量通常會有所升高。
full GC:當(dāng)準(zhǔn)備要觸發(fā)一次young GC時,如果發(fā)現(xiàn)統(tǒng)計數(shù)據(jù)說之前young GC的平均晉升大小比目前old gen剩余的空間大,則不會觸發(fā)young GC而是轉(zhuǎn)為觸發(fā)full GC(因為HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,所以不需要事先觸發(fā)一次單獨的young GC);或者,如果有perm gen的話,要在perm gen分配空間但已經(jīng)沒有足夠空間時,也要觸發(fā)一次full GC;或者System.gc()、heap dump帶GC,默認(rèn)也是觸發(fā)full GC。
除直接調(diào)用System.gc外,觸發(fā)Full GC執(zhí)行的情況有如下四種。
舊生代空間不足
舊生代空間只有在新生代對象轉(zhuǎn)入及創(chuàng)建為大對象、大數(shù)組時才會出現(xiàn)不足的現(xiàn)象,當(dāng)執(zhí)行Full GC后空間仍然不足,則拋出如下錯誤:
java.lang.OutOfMemoryError: Java heap space
為避免以上兩種狀況引起的Full GC,調(diào)優(yōu)時應(yīng)盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創(chuàng)建過大的對象及數(shù)組。
2. Permanet Generation空間滿
Permanet Generation中存放的為一些class的信息等,當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執(zhí)行Full GC。如果經(jīng)過Full GC仍然回收不了,那么JVM會拋出如下錯誤信息:
java.lang.OutOfMemoryError: PermGen space
為避免Perm Gen占滿造成Full GC現(xiàn)象,可采用的方法為增大Perm Gen空間或轉(zhuǎn)為使用CMS GC。
3. CMS GC時出現(xiàn)promotion failed和concurrent mode failure
對于采用CMS進(jìn)行舊生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當(dāng)這兩種狀況出現(xiàn)時可能會觸發(fā)Full GC。
promotion failed是在進(jìn)行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執(zhí)行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。
應(yīng)對措施為:增大survivor space、舊生代空間或調(diào)低觸發(fā)并發(fā)GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由于JDK的bug29導(dǎo)致CMS在remark完畢后很久才觸發(fā)sweeping動作。對于這種狀況,可通過設(shè)置-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。
統(tǒng)計得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間
這是一個較為復(fù)雜的觸發(fā)情況,Hotspot為了避免由于新生代對象晉升到舊生代導(dǎo)致舊生代空間不足的現(xiàn)象,在進(jìn)行Minor GC時,做了一個判斷,如果之前統(tǒng)計所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間,那么就直接觸發(fā)Full GC。
例如程序第一次觸發(fā)Minor GC后,有6MB的對象晉升到舊生代,那么當(dāng)下一次Minor GC發(fā)生時,首先檢查舊生代的剩余空間是否大于6MB,如果小于6MB,則執(zhí)行Full GC。
當(dāng)新生代采用PS GC時,方式稍有不同,PS GC是在Minor GC后也會檢查,例如上面的例子中第一次Minor GC后,PS GC會檢查此時舊生代的剩余空間是否大于6MB,如小于,則觸發(fā)對舊生代的回收。
除了以上4種狀況外,對于使用RMI來進(jìn)行RPC或管理的Sun JDK應(yīng)用而言,默認(rèn)情況下會一小時執(zhí)行一次Full GC??赏ㄟ^在啟動時通過- java -Dsun.rmi.dgc.client.gcInterval=3600000來設(shè)置Full GC執(zhí)行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調(diào)用System.gc。
Minor GC ,F(xiàn)ull GC 觸發(fā)條件
Minor GC觸發(fā)條件:當(dāng)Eden區(qū)滿時,觸發(fā)Minor GC。
Full GC觸發(fā)條件:
(1)調(diào)用System.gc時,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
(2)老年代空間不足
(3)方法去空間不足
(4)通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
(5)由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時,對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小
Java中Stop-The-World機(jī)制簡稱STW,是在執(zhí)行垃圾收集算法時,Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫?,F(xiàn)象,全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能與JVM交互;這些現(xiàn)象多半是由于gc引起。
GC時的Stop the World(STW)是大家最大的敵人。但可能很多人還不清楚,除了GC,JVM下還會發(fā)生停頓現(xiàn)象。
JVM里有一條特殊的線程--VM Threads,專門用來執(zhí)行一些特殊的VM Operation,比如分派GC,thread dump等,這些任務(wù),都需要整個Heap,以及所有線程的狀態(tài)是靜止的,一致的才能進(jìn)行。所以JVM引入了安全點(Safe Point)的概念,想辦法在需要進(jìn)行VM Operation時,通知所有的線程進(jìn)入一個靜止的安全點。
除了GC,其他觸發(fā)安全點的VM Operation包括:
1. JIT相關(guān),比如Code deoptimization, Flushing code cache ;
2. Class redefinition (e.g. javaagent,AOP代碼植入的產(chǎn)生的instrumentation) ;
3. Biased lock revocation 取消偏向鎖 ;
4. Various debug operation (e.g. thread dump or deadlock check);
垃圾回收是一種回收無用內(nèi)存空間并使其對未來實例可用的過程。
Eden 區(qū):當(dāng)一個實例被創(chuàng)建了,首先會被存儲在堆內(nèi)存年輕代的 Eden 區(qū)中。
注意:如果你不能理解這些詞匯,我建議你閱讀這篇 垃圾回收介紹 ,這篇教程詳細(xì)地介紹了內(nèi)存模型、JVM 架構(gòu)以及這些術(shù)語。
Survivor 區(qū)(S0 和 S1):作為年輕代 GC(Minor GC)周期的一部分,存活的對象(仍然被引用的)從 Eden 區(qū)被移動到 Survivor 區(qū)的 S0 中。類似的,垃圾回收器會掃描 S0 然后將存活的實例移動到 S1 中。
(譯注:此處不應(yīng)該是Eden和S0中存活的都移到S1么,為什么會先移到S0再從S0移到S1?)
死亡的實例(不再被引用)被標(biāo)記為垃圾回收。根據(jù)垃圾回收器(有四種常用的垃圾回收器,將在下一教程中介紹它們)選擇的不同,要么被標(biāo)記的實例都會不停地從內(nèi)存中移除,要么回收過程會在一個單獨的進(jìn)程中完成。
老年代: 老年代(Old or tenured generation)是堆內(nèi)存中的第二塊邏輯區(qū)。當(dāng)垃圾回收器執(zhí)行 Minor GC 周期時,在 S1 Survivor 區(qū)中的存活實例將會被晉升到老年代,而未被引用的對象被標(biāo)記為回收。
老年代 GC(Major GC):相對于 Java 垃圾回收過程,老年代是實例生命周期的最后階段。Major GC 掃描老年代的垃圾回收過程。如果實例不再被引用,那么它們會被標(biāo)記為回收,否則它們會繼續(xù)留在老年代中。
內(nèi)存碎片:一旦實例從堆內(nèi)存中被刪除,其位置就會變空并且可用于未來實例的分配。這些空出的空間將會使整個內(nèi)存區(qū)域碎片化。為了實例的快速分配,需要進(jìn)行碎片整理?;诶厥掌鞯牟煌x擇,回收的內(nèi)存區(qū)域要么被不停地被整理,要么在一個單獨的GC進(jìn)程中完成。
在釋放一個實例和回收內(nèi)存空間之前,Java 垃圾回收器會調(diào)用實例各自的
finalize()
方法,從而該實例有機(jī)會釋放所持有的資源。雖然可以保證
finalize()
會在回收內(nèi)存空間之前被調(diào)用,但是沒有指定的順序和時間。多個實例間的順序是無法被預(yù)知,甚至可能會并行發(fā)生。程序不應(yīng)該預(yù)先調(diào)整實例之間的順序并使用
finalize()
方法回收資源。
任何在 finalize過程中未被捕獲的異常會自動被忽略,然后該實例的 finalize 過程被取消。
JVM 規(guī)范中并沒有討論關(guān)于弱引用的垃圾回收機(jī)制,也沒有很明確的要求。具體的實現(xiàn)都由實現(xiàn)方?jīng)Q定。
垃圾回收是由一個守護(hù)線程完成的。
所有實例都沒有活動線程訪問。
沒有被其他任何實例訪問的循環(huán)引用實例。
Java 中有不同的引用類型。判斷實例是否符合垃圾收集的條件都依賴于它的引用類型。
引用類型 | 垃圾收集 |
---|---|
強(qiáng)引用(Strong Reference) | 不符合垃圾收集 |
軟引用(Soft Reference) | 垃圾收集可能會執(zhí)行,但會作為最后的選擇 |
弱引用(Weak Reference) | 符合垃圾收集 |
虛引用(Phantom Reference) | 符合垃圾收集 |
在編譯過程中作為一種優(yōu)化技術(shù),Java 編譯器能選擇給實例賦
null
值,從而標(biāo)記實例為可回收。
class Animal { public static void main(String[] args) { Animal lion = new Animal(); System.out.println("Main is completed."); } protected void finalize() { System.out.println("Rest in Peace!"); } }
在上面的類中,lion
對象在實例化行后從未被使用過。因此 Java 編譯器作為一種優(yōu)化措施可以直接在實例化行后賦值lion = null
。因此,即使在 SOP 輸出之前, finalize 函數(shù)也能夠打印出
'Rest in Peace!'
。我們不能證明這確定會發(fā)生,因為它依賴JVM的實現(xiàn)方式和運行時使用的內(nèi)存。然而,我們還能學(xué)習(xí)到一點:如果編譯器看到該實例在未來再也不會被引用,能夠選擇并提早釋放實例空間。
關(guān)于對象什么時候符合垃圾回收有一個更好的例子。實例的所有屬性能被存儲在寄存器中,隨后寄存器將被訪問并讀取內(nèi)容。無一例外,這些值將被寫回到實例中。雖然這些值在將來能被使用,這個實例仍然能被標(biāo)記為符合垃圾回收。這是一個很經(jīng)典的例子,不是嗎?
當(dāng)被賦值為null時,這是很簡單的一個符合垃圾回收的示例。當(dāng)然,復(fù)雜的情況可以像上面的幾點。這是由 JVM 實現(xiàn)者所做的選擇。目的是留下盡可能小的內(nèi)存占用,加快響應(yīng)速度,提高吞吐量。為了實現(xiàn)這一目標(biāo), JVM 的實現(xiàn)者可以選擇一個更好的方案或算法在垃圾回收過程中回收內(nèi)存空間。
當(dāng)
finalize()
方法被調(diào)用時,JVM 會釋放該線程上的所有同步鎖。
Class GCScope { GCScope t; static int i = 1; public static void main(String args[]) { GCScope t1 = new GCScope(); GCScope t2 = new GCScope(); GCScope t3 = new GCScope(); // No Object Is Eligible for GC t1.t = t2; // No Object Is Eligible for GC t2.t = t3; // No Object Is Eligible for GC t3.t = t1; // No Object Is Eligible for GC t1 = null; // No Object Is Eligible for GC (t3.t still has a reference to t1) t2 = null; // No Object Is Eligible for GC (t3.t.t still has a reference to t2) t3 = null; // All the 3 Object Is Eligible for GC (None of them have a reference. // only the variable t of the objects are referring each other in a // rounded fashion forming the Island of objects with out any external // reference) } protected void finalize() { System.out.println("Garbage collected from object" + i); i++; } class GCScope { GCScope t; static int i = 1; public static void main(String args[]) { GCScope t1 = new GCScope(); GCScope t2 = new GCScope(); GCScope t3 = new GCScope(); // 沒有對象符合GC t1.t = t2; // 沒有對象符合GC t2.t = t3; // 沒有對象符合GC t3.t = t1; // 沒有對象符合GC t1 = null; // 沒有對象符合GC (t3.t 仍然有一個到 t1 的引用) t2 = null; // 沒有對象符合GC (t3.t.t 仍然有一個到 t2 的引用) t3 = null; // 所有三個對象都符合GC (它們中沒有一個擁有引用。 // 只有各對象的變量 t 還指向了彼此, // 形成了一個由對象組成的環(huán)形的島,而沒有任何外部的引用。) } protected void finalize() { System.out.println("Garbage collected from object" + i); i++; }
在判斷哪些內(nèi)存需要回收和什么時候回收用到GC 算法,本文主要對GC 算法進(jìn)行講解。
常見的JVM垃圾判定算法包括:引用計數(shù)算法、可達(dá)性分析算法。
引用計數(shù)算法是通過判斷對象的引用數(shù)量來決定對象是否可以被回收。
給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器值就加1;當(dāng)引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再被使用的。
優(yōu)點:簡單,高效,現(xiàn)在的objective-c用的就是這種算法。
缺點:很難處理循環(huán)引用,相互引用的兩個對象則無法釋放。因此目前主流的Java虛擬機(jī)都摒棄掉了這種算法。
舉個簡單的例子,對象objA和objB都有字段instance,賦值令objA.instance=objB及objB.instance=objA,除此之外,這兩個對象沒有任何引用,實際上這兩個對象已經(jīng)不可能再被訪問,但是因為互相引用,導(dǎo)致它們的引用計數(shù)都不為0,因此引用計數(shù)算法無法通知GC收集器回收它們。
public class ReferenceCountingGC { public Object instance = null; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc();//GC } }
運行結(jié)果
[GC (System.gc()) [PSYoungGen: 3329K->744K(38400K)] 3329K->752K(125952K), 0.0341414 secs] [Times: user=0.00 sys=0.00, real=0.06 secs] [Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->628K(87552K)] 752K->628K(125952K), [Metaspace: 3450K->3450K(1056768K)], 0.0060728 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] Heap PSYoungGen total 38400K, used 998K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000) eden space 33280K, 3% used [0x00000000d5c00000,0x00000000d5cf9b20,0x00000000d7c80000) from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000) to space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000) ParOldGen total 87552K, used 628K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000) object space 87552K, 0% used [0x0000000081400000,0x000000008149d2c8,0x0000000086980000) Metaspace used 3469K, capacity 4496K, committed 4864K, reserved 1056768K class space used 381K, capacity 388K, committed 512K, reserved 1048576K Process finished with exit code 0
從運行結(jié)果看,GC日志中包含“3329K->744K”,意味著虛擬機(jī)并沒有因為這兩個對象互相引用就不回收它們,說明虛擬機(jī)不是通過引用技術(shù)算法來判斷對象是否存活的。
可達(dá)性分析算法是通過判斷對象的引用鏈?zhǔn)欠窨蛇_(dá)來決定對象是否可以被回收。
從GC Roots(每種具體實現(xiàn)對GC Roots有不同的定義)作為起點,向下搜索它們引用的對象,可以生成一棵引用樹,樹的節(jié)點視為可達(dá)對象,反之視為不可達(dá)。
在Java語言中,可以作為GC Roots的對象包括下面幾種:
虛擬機(jī)棧(棧幀中的本地變量表)中的引用對象。
方法區(qū)中的類靜態(tài)屬性引用的對象。
方法區(qū)中的常量引用的對象。
本地方法棧中JNI(Native方法)的引用對象
真正標(biāo)記以為對象為可回收狀態(tài)至少要標(biāo)記兩次。
強(qiáng)引用就是指在程序代碼之中普遍存在的,類似”O(jiān)bject obj = new Object()”這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。
Object obj = new Object();
軟引用是用來描述一些還有用但并非必需的對象,對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。在JDK1.2之后,提供了SoftReference類來實現(xiàn)軟引用。
Object obj = new Object(); SoftReference
弱引用也是用來描述非必需對象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象,只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。在JDK1.2之后,提供了WeakReference類來實現(xiàn)弱引用。
Object obj = new Object(); WeakReferencewf = new WeakReference (obj);
虛引用也成為幽靈引用或者幻影引用,它是最弱的一中引用關(guān)系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。在JDK1.2之后,提供給了PhantomReference類來實現(xiàn)虛引用。
Object obj = new Object(); PhantomReferencepf = new PhantomReference (obj);
常見的垃圾回收算法包括:標(biāo)記-清除算法,復(fù)制算法,標(biāo)記-整理算法,分代收集算法。
在介紹JVM垃圾回收算法前,先介紹一個概念。
Stop-the-World
Stop-the-world意味著 JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行,并且這種情形會在任何一種GC算法中發(fā)生。當(dāng)Stop-the-world發(fā)生時,除了GC所需的線程以外,所有線程都處于等待狀態(tài)直到GC任務(wù)完成。事實上,GC優(yōu)化很多時候就是指減少Stop-the-world發(fā)生的時間,從而使系統(tǒng)具有高吞吐 、低停頓的特點。
之所以說標(biāo)記/清除算法是幾種GC算法中最基礎(chǔ)的算法,是因為后續(xù)的收集算法都是基于這種思路并對其不足進(jìn)行改進(jìn)而得到的。標(biāo)記/清除算法的基本思想就跟它的名字一樣,分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。
標(biāo)記階段:標(biāo)記的過程其實就是前面介紹的可達(dá)性分析算法的過程,遍歷所有的GC Roots對象,對從GC Roots對象可達(dá)的對象都打上一個標(biāo)識,一般是在對象的header中,將其記錄為可達(dá)對象;
清除階段:清除的過程是對堆內(nèi)存進(jìn)行遍歷,如果發(fā)現(xiàn)某個對象沒有被標(biāo)記為可達(dá)對象(通過讀取對象header信息),則將其回收。
不足:
標(biāo)記和清除過程效率都不高
會產(chǎn)生大量碎片,內(nèi)存碎片過多可能導(dǎo)致無法給大對象分配內(nèi)存。
將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了就將還存活的對象復(fù)制到另一塊上面,然后再把使用過的內(nèi)存空間進(jìn)行一次清理。
現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來回收新生代,但是并不是將內(nèi)存劃分為大小相等的兩塊,而是分為一塊較大的 Eden 空間和兩塊較小的 Survior 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的對象一次性復(fù)制到另一塊 Survivor 空間上,最后清理 Eden 和 使用過的那一塊 Survivor。HotSpot 虛擬機(jī)的 Eden 和 Survivor 的大小比例默認(rèn)為 8:1,保證了內(nèi)存的利用率達(dá)到 90 %。如果每次回收有多于 10% 的對象存活,那么一塊 Survivor 空間就不夠用了,此時需要依賴于老年代進(jìn)行分配擔(dān)保,也就是借用老年代的空間。
不足:
將內(nèi)存縮小為原來的一半,浪費了一半的內(nèi)存空間,代價太高;如果不想浪費一半的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
復(fù)制收集算法在對象存活率較高時就要進(jìn)行較多的復(fù)制操作,效率將會變低。
標(biāo)記—整理算法和標(biāo)記—清除算法一樣,但是標(biāo)記—整理算法不是把存活對象復(fù)制到另一塊內(nèi)存,而是把存活對象往內(nèi)存的一端移動,然后直接回收邊界以外的內(nèi)存,因此其不會產(chǎn)生內(nèi)存碎片。標(biāo)記—整理算法提高了內(nèi)存的利用率,并且它適合在收集對象存活時間較長的老年代。
不足:
效率不高,不僅要標(biāo)記存活對象,還要整理所有存活對象的引用地址,在效率上不如復(fù)制算法。
分代回收算法實際上是把復(fù)制算法和標(biāo)記整理法的結(jié)合,并不是真正一個新的算法,一般分為:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要進(jìn)行回收的,新生代就是有很多的內(nèi)存空間需要回收,所以不同代就采用不同的回收算法,以此來達(dá)到高效的回收算法。
新生代:由于新生代產(chǎn)生很多臨時對象,大量對象需要進(jìn)行回收,所以采用復(fù)制算法是最高效的。
老年代:回收的對象很少,都是經(jīng)過幾次標(biāo)記后都不是可回收的狀態(tài)轉(zhuǎn)移到老年代的,所以僅有少量對象需要回收,故采用標(biāo)記清除或者標(biāo)記整理算法。
到此,關(guān)于“JVM垃圾回收基本原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
新聞名稱:JVM垃圾回收基本原理是什么
文章地址:http://weahome.cn/article/jspsdj.html