垃圾收集與內存分配

垃圾回收須要關注的問題:哪些內存須要回收?何時回收?如何回收?算法

判斷內存是否須要回收:安全

一、引用計數法:缺點:循環引用,兩個對象互相引用,永遠沒法回收,即使已經應該回收了。多線程

二、可達性分析算法:經過一系列成爲GC Roots的對象做爲起始點,從這些節點開始向下搜索,全部所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。其中能夠做爲GC Roots的對象包括:併發

  • 虛擬機棧(棧幀的本地變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI(Native方法)引用的對象。

再談引用:this

在JDK1.2以前,Java中的引用的定義是若是reference類型的數據中存儲的數值表明的是另外一塊內存的起始地址,那麼這塊內存表明着一個引用。這樣的方式對於對象的定義就只有被引用和沒有引用的兩種,可是對於一些內存空間足夠的時候能夠保存,內存緊張就要刪除的對象就沒有辦法表示。線程

在JDK1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用、軟引用、弱引用、虛引用4種,這4種引用強度依次減弱。日誌

強引用是指程序代碼種廣泛存在的,相似new出來的這類引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象。對象

軟引用是用來描述一些還有用但並不是必需的對象。對於軟引用的對象,在系統要發生內存溢出異常以前,會列入回收範圍進行二次回收。SoftReference。blog

弱引用也是非必需對象,弱引用只能保留到下一次垃圾回收以前。WeakReference。隊列

虛引用也稱幽靈引用或者幻影引用。一個對象是否有虛引用不會對它的生存時間構成影響,也沒法經過虛引用獲取一個對象實例。虛引用的目的就是在這個對象被收集器回收時收到一個系統通知。PhantomReference。

survive Or Death

在可達性分析算法種不可達的對象,並不是立刻要銷燬回收,須要宣告一個對象銷燬死亡,至少要經歷兩次標記過程:若是在一次可達性分析後發現沒有與GC Roots相連的引用鏈,它會被第一次標記而且進行一次篩選,篩選的條件時此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize方法,或者這個方法已經被調用過了,虛擬機將這兩種狀況視爲沒有必要。

若是斷定爲必要,對象會被放在F-Queue隊列中,並在稍後由一個虛擬機自動創建的、低優先級的Finalizer線程去執行它。執行只是觸發finalize方法,不會保證執行結束。finalize方法時對象逃脫死亡的最後機會,稍後GC會對F-Queue中的對象進行第二次小規模的標記,若是對象在finalize中拯救本身(從新與引用鏈上的任何一個對象創建關聯,例如把本身的this賦值給某個對象的類變量或者成員變量),那麼第二次標記的時候就會被移除這個集合裏,若是此次尚未逃逸,就會被真的回收了。

回收方法區:

Java虛擬機規範不要求虛擬機在方法區實現垃圾收集,並且在方法區中進行垃圾收集的性價比比較低。堆中的新生代通常能夠回收70%~95%的空間,而永久代的收集效率遠低於此。

永久代的垃圾回收主要回收兩部分:廢棄常量和無用的類。

廢棄常量:當前系統的沒有任何地方引用了這個常量。

無用的類:該類的全部實例、類加載器、Class對象沒有在任何地方被引用。

垃圾收集算法:

標記-清除算法,首先標記全部要回收的對象,標記結束後統一回收全部被標記的對象。缺點:效率問題,標記和清除的效率不高;空間問題,標記清除後會產生大量不連續的空間,碎片過多,大對象申請連續的空間的時候,會提早觸發另外一次垃圾收集的動做。

複製算法:

爲了解決效率的問題,使用複製算法。將內容劃分爲大小相等的兩塊,每次只使用其中的一塊。一塊內存用完了,就將還存活的對象複製到另外一塊上面,而後把已使用的空間一次清理掉。簡單高效,可是內存被縮小了一半,成本太高。如今商業虛擬機都採用追蹤收集算法回收新生代,IBM公司研究代表,新生代98%對象存活時間很短,不用1:1的比例,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空概念,每次使用Eden和其中一塊Servivor。當垃圾回收時,將Eden和Survivor空間中還存活的對象一次性的複製到另外一塊Survivor空間上,最後清理掉Survivor空間。HotSpot默認Eden和Survivor比例時8:1,也就是每次新生代中可用的內存空間時整個新生代容量的90%。若是多餘10%存活,須要以來老年代進行分配擔保。

標記-整理算法:

複製收集算法在對象存活率較高的時候須要進行較多的複製操做,效率比較低。若是使用複製算法不想浪費50%的內存空間,就須要額外的空間進行分配擔保,以應對被使用的內存中全部的對象100%存活的極端狀況,老年代通常不採用這種算法。

根據老年代的特色,有人提出了標記整理算法,標記過程和標記清除算法同樣,後續步驟採用存活的對象向一端移動,而後清理掉邊界外的內存。

分代收集算法:

當前的商業虛擬機都採用分代收集算法,根據對象的存活週期將內存分爲幾塊。通常Java堆分爲新生代和老年代,而後根據各個年代的特色採用最適當的收集算法。新生代每次垃圾收集會有大批對象死去,採用複製算法。老年代存活率高、沒有額外空間對它進行分配擔保,能夠採用標記清理或者標記整理算法回收。

HotSpot算法

枚舉根節點

以可達性分析中從GC Roots節點找引用鏈這個操做爲例,可做爲GC Roots的節點主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表),如今不少應用僅僅方法區就有數百兆,若是要逐個檢查這裏面的引用,那麼必然會消耗不少時間。

另外,可達性分析對執行時間的敏感還體如今GC停頓上,由於這項分析工做必須在一個能保證一致性的快照中進行(這裏的一致性指整個分析期間整個系統看起來像凍結在了某個時間點上),這點是致使GC進行時必須停頓全部的的Java執行線程(Sun將這件事稱爲Stop The World)的其中一個重要緣由,即便在號稱幾乎不會發生停頓的CMS收集器中,枚舉根節點時也是要必須進行停頓的。主流Java虛擬機使用的都是準確式GC,因此當系統停頓下來後,並不須要一個不漏的檢查完全部執行上下文和全局的引用位置,虛擬機應當時有辦法直接得知那些地方存放着對象引用。

安全點

在OopMap的協助下,HotSpot能夠快速準確的完成GC Roots枚舉,若是每條指令都生成OopMap會有大量的額外空間。只有在安全點才能夠停頓下來GC。安全點的選定是以程序「是否具備讓程序長時間執行的特徵」爲標準選定的,長時間執行比較明顯的特徵就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等,具備這些功能的執行纔會產生Safepoint。

須要考慮在GC發生時,讓全部線程跑到最近的安全點上停下。搶先式中斷和主動式中斷。

搶先式中斷:當GC發生時,首先讓全部線程停頓,若是有線程沒有執行到最近的安全點,就恢復線程。(幾乎沒有虛擬機採用)

主動式中斷:當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單的設置一個標誌,各個線程執行時主動的去輪詢這個標誌,當發現中斷標誌爲真時就本身中斷掛起。

安全區域

安全點沒法解決一個線程沒有分配CPU時間的狀況,一個線程若是沒有執行就沒法到達安全點。安全區是指在一段代碼片斷中,引用關係不會發生變化。這個區域的任何地方GC都是安全的。線程在執行Safe Region中的代碼時,首先標誌本身已經進入了Safe Region,當JVM要發起GC,就不用管標誌爲Safe Region狀態的線程了。線程要離開時,會檢查系統是否完成了GC Roots枚舉(或是整個GC過程),若是完成了,線程繼續,不然就要等到能夠安全離開安全區的信號爲止。

垃圾收集器(圖片來自《深刻理解Java虛擬機》)

Serial收集器

Serial收集器時最基本、發展歷史最悠久的收集器。單線程收集器,進行GC的時候,必須暫停其餘全部的工做線程,直到收集結束。Stop The World是由JVM在後臺自動發起完成的,在用戶不可見的把用戶正常的線程所有停掉。(新生代採用複製算法老年代採用標記整理算法)單核下簡單高效,Client模式下的虛擬機是個很好的選擇。

ParNew收集器

是Serial收集器的多線程版本,採用多線程進行垃圾收集。(複製算法)

Parallel Scavenge

新生代收集器,也是複製算法,並行多線程。

目標在於達到一個可控的吞吐量。吞吐量=用戶代碼時間/(用戶代碼 + 垃圾收集)。GC停頓短適合於與用戶交互的程序,高吞吐量能夠高效利用CPU時間,儘快完成程序的運算任務,適合在後臺運算而不須要太多交互的任務。

Serial Old收集器

Serial老年代版本,單線程,標記-整理算法。

Parallel Old收集器

ParallelScavenge收集器的老年代版本,多線程,標記整理。

CMS收集器

CMS(Concurrent Mark Sweep)以獲取最短回收停頓時間爲目標的收集器。標記清除算法。

  • 初識標記
  • 併發標記
  • 從新標記
  • 併發清除

其中初識標記和從新標記仍須要Stop The World。

整個過程耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做,整體上看收集器和用戶線程基本是併發執行的。

CMS收集器對於CPU資源很敏感。會搶佔處理器資源。

CMS沒法處理浮動垃圾,會出現Concurrent Mode Failure而致使另外一次Full GC。因爲收集器收集的時候,用戶線程還在執行,會致使新的垃圾產生,這些垃圾出如今標記過程以後只能等下次收集,這些垃圾稱爲浮動垃圾。因爲收集垃圾的時候,用戶線程還在執行,就須要預留內存給用戶線程。當發生Concurrent Mode Failure時,會臨時啓用Serial Old收集。因爲採用的時標記清除,會致使大量碎片,大對象沒法分配連續的內存,就會觸發Full GC。CMS提供參數供Full GC的時候進行碎片整理和多少次不壓縮的Full GC進行碎片整理。

G1收集器

面向服務端的垃圾收集器。並行與併發,分代收集(獨自管理整個GC堆),空間整合(標記整理),可預測的停頓。

G1收集器將整個堆分爲多個大小相等的獨立區域,保留新生代和老年代,但並不是徹底物理隔離,它們都是一部分Region(不須要連續)的集合。

G1能夠創建可預測的停頓時間模型,是由於它能夠有計劃的避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小,在後臺維護一個優先列表,根據收集時間,優先回收價值最大的。使用Region劃份內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內能夠獲取儘量高的收集效率。

  • 初識標記
  • 併發標記
  • 最終標記
  • 篩選回收

GC日誌

Full GC 表明發生了STW。

GC發生時間 : GC類型:收集器類型:內存回收狀況,GC用時。

內存分配與回收策略

對象的內存分配,主要是堆上分配,對象主要分配在新生代的Eden區上,若是啓動了本地線程分配緩衝,優先分配在TLAB上。少數狀況也會直接分配在老年代。

相關文章
相關標籤/搜索