一、內(nèi)存回收的關(guān)注區(qū)域
JMM章節(jié)中介紹了Java虛擬機內(nèi)存模型的幾個區(qū)域,對于程序計數(shù)器、虛擬機棧和本地方法棧都是線程私有的,伴隨著線程由生到滅,這幾個區(qū)域的內(nèi)存分配這回收都有一定確定性(因為所占內(nèi)存大小基本是編譯可知的),因此,內(nèi)存回收主要的關(guān)注對象是堆和方法區(qū)。我們只有在程序運行時才能確定會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的。垃圾收集器主要關(guān)注的是這部分內(nèi)存。
二、對象可被回收的判斷標準
-
引用計數(shù)法
思想:就是對每個對象添加一個引用計數(shù)器,使用該算法的微軟的com技術(shù)等
弊端:這種方式存在一個問題就是循環(huán)引用,導致內(nèi)存泄露 -
可達性分析法
思想:就是通過一系列稱為GC Roots的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈。
Java語言中,可以作為GC Roots對象包括下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 本地方法棧中(Native方法)引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
-
引用相關(guān)知識
一般意義上講,引用就代表了一個對象的內(nèi)存地址或者存有沒有對象地址的句柄,但是這種定義導致一個對象只存在引用和未被引用兩種狀態(tài)。而在實際應用中,一個對象需要更多的狀態(tài),來提高效率和優(yōu)化性能。因此,Java引用分為4種:
- 強引用:代碼中最常見的 Object obj = new Object();只要引用還存在,垃圾收集器永遠不會回收被引用的對象。
- 軟引用:用來描述一些還有用但是非必須的對象。對于軟引用關(guān)聯(lián)的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會將這些對象列進可回收范圍之中進行第二次回收,如果這次回收還是沒有足夠的內(nèi)存,那么才會拋出內(nèi)存溢出異常。通過SoftReference來實現(xiàn)。
- 弱引用:也是用來描述非必須的對象。弱引用只能生存到下一次垃圾收集之前。當垃圾收集器開始工作時,無論內(nèi)存是否足夠,都會回收這部分內(nèi)存。通過WeakReference來實現(xiàn)。
- 虛引用:一個對象是否有虛引用,完全不會對其生存周期構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用的唯一目的就是能在這個對象被垃圾收集器回收時能夠收到一個系統(tǒng)通知。通過PhantomReference來實現(xiàn)。
-
對象是否直接死亡
對于可達性分析算法中不可達的對象,也不是“非死不可”,要真正宣告對象死亡,必須經(jīng)歷兩次標記過程。如果可達性分析不可達時,對象會被第一次標記并且進行一次篩選,篩選標準是對象是否有必要執(zhí)行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機調(diào)用過,虛擬機都將這兩種情況視為沒有必要執(zhí)行。如果這個對象被判定位必要執(zhí)行finalize()方法,則會被放在一個F-Queue隊列中,并且會在稍后由一個虛擬機自建的Finalize線程去執(zhí)行它。一個對象的finalize()方法只會被執(zhí)行一次,一個對象可以在finalize()完成一次自救,并且只能自救一次。
三、垃圾回收
-
回收方法區(qū)
方法區(qū)的回收對象主要是廢棄常量和無用的類。對于一個字符串常量“hello”,如果當前系統(tǒng)中沒有任何一個String對象叫做“hello”,那么就認為沒有任何對象引用常量池中的“hello”常量,這時該常量就是廢棄常量。無用的類的判斷標準則比較復雜,需要滿足一下條件:
- 該類的所有實例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實例。
- 加載該類的ClassLoader已經(jīng)被回收。
- 該類對應的java.lang.Class對象沒有任何地方引用,無法在任何地方通過反射來訪問該類的方法。
滿足上面三個條件的無用類可以被回收,但是并不是必然回收。還需要看虛擬機參數(shù)設置
-
垃圾收集算法
- 標記-清除算法
思想:首先標記出所偶遇需要回收的對象,標記完成之后進行統(tǒng)一回收。
弊端:標記和清除的效率很低,另外就是會產(chǎn)生大量不連續(xù)的碎片,碎片過多可能會導致后續(xù)對象分配內(nèi)存時,無法找到適合內(nèi)存空間而進一步促發(fā)下一次垃圾收集動作 - 復制算法
將可用內(nèi)存一份為二,每次使用其中一塊,當一塊內(nèi)存使用完之后,將還存活的對象復制到另外一塊上,然后將使用的這一塊給一次清理掉。
弊端:將可用內(nèi)存空間一半空閑,代價過高
實際使用中大多數(shù)對象都朝生夕滅,所以優(yōu)化方法就是講內(nèi)存分配默認Eden、和兩塊相等的Survivor空間8:1:1,每次垃圾回收時將還存活的對象復制到空閑的那一塊survivor中,這樣每次就只會浪費10%的內(nèi)存空間。當然,當survivor不夠保存剩余存活的對象時,需要到老年代擔保,也就是當超出時,將對象放到老年代。 - 標記-整理算法
標記過程和標記-清除算法一樣,只是在清除時,會將所有存活的對象移動到一端,然后直接清理端外界的的內(nèi)存。
-
垃圾收集器
- Serial收集器
這個收集器是一個單線程的收集器,這里單線程的意思是當GC線程開始工作時,其他的用戶線程都必須暫停,當?shù)竭_安全點之后,GC線程新生代中采用復制算法,老年代中采用標記-整理算法進行垃圾回收
弊端:需要暫停,非常影響體驗
優(yōu)勢:簡單高效,不會有線程交互的開銷,如果停頓時間控制得當,不要頻繁發(fā)生,則還可以接受,一般用于運行在Client模式的虛擬機。 - ParNew收集器
多線程版本的Serial收集器。使用多線程來進行垃圾回收。許多運行在server模式的虛擬機首選新生代收集器,原因是,除了Serial以外,只有它可以與CMS收集器配合使用。 - Parallel Scavenge收集器
CMS等收集器的關(guān)注點是如何減少垃圾回收時,用戶線程的等待時間,而Parallel Scavenge收集器則是達到一個可控制的吞吐量 吞吐量 = 執(zhí)行用戶代碼時間/(執(zhí)行用戶代碼時間+垃圾收集時間)。停頓時間短適合交互性比較強,需要良好的響應速度來提升用戶體驗,而高吞吐量則可以高效的利用cpu時間,盡快完成程序的運算任務。 - Serial Old收集器
Serial收集器的老年代版本,使用標記-整理方法,一個是與Parallel Scavenge配合使用,第二點就是作為CMS的后備方案 - Parallel Old收集器
Parallel Scavenge的老年代版本,它出現(xiàn)之前,新生代的收集器Parallel只能和Serial Old配合使用 - CMS收集器
這種收集器主要獲取最短回收停頓時間為目標,主要用于互聯(lián)網(wǎng)站等系統(tǒng)的服務端。標記-清除算法實現(xiàn)。其垃圾收集的過程分為四步:
初始標記:主要標記GC root能夠直接關(guān)聯(lián)的對象,時間短
并發(fā)標記:GC root tracing的過程,時間長,但是與用戶線程并發(fā)執(zhí)行
重新標記:修正并發(fā)標記過程中因用戶程序繼續(xù)運用而產(chǎn)生變動的那一部分對象,時間短
并發(fā)清除:時間長,與用戶線程并發(fā)執(zhí)行
缺點:對cpu資源非常敏感,因為并發(fā)執(zhí)行階段會占用線程執(zhí)行的cpu資源,當cpu較少時會導致系統(tǒng)吞吐量很低。無法處理浮動的垃圾,可能會出現(xiàn)(concurrent mode failure)失敗而導致另一次full gc的出現(xiàn),在并發(fā)清除階段用戶線程還會生產(chǎn)垃圾。還有一個缺點就是標記-清除,會產(chǎn)生大量空間碎片。 - G1收集器
是一款面向服務端的垃圾收集器,特點:并發(fā)和并行,分代收集,空間整合,整體上看像標記-整理,局部像復制,可預測的停頓。它將整個堆分為多個大小相等的區(qū)域,雖然保留了新生代和老年代的概念,但是不再是隔離的了。跟蹤每個區(qū)域垃圾堆積的大小,每次回收時,優(yōu)先回收價值最大的region。
垃圾收集的步驟:
初始標記:
并發(fā)標記:
最終標記:
篩選回收:
四個過程和cms垃圾收集器很類似,最后一步不同在于,g1是挑選最具有回收的價值的區(qū)域開始回收
4.GC 日志
關(guān)鍵點:GC、Full GC、回收前的容量、回收后的容量、總的容量、回收的區(qū)域、回收時間a