要點提煉| 理解JVM之GC&內存分配

有內存分配就會有內存回收,上篇也瞭解到Java堆是垃圾收集器管理的主要區域,本篇將理解這部份內存的垃圾回收機制。

  • 對象存活斷定算法
  • 垃圾收集算法
  • HotSpot算法實現&垃圾收集器
  • 內存分配與回收策略

1.對象存活斷定算法java

概念:引用的四種類型算法

  • 強引用(StrongReference)
    • 具備強引用的對象不會被GC;
    • 即使內存空間不足,JVM寧願拋出OutOfMemoryError使程序異常終止,也不會隨意回收具備強引用的對象。
  • 軟引用(SoftReference)
    • 只具備軟引用的對象,會在內存空間不足的時候被GC;
    • 軟引用經常使用來實現內存敏感的高速緩存
  • 弱引用(WeakReference)
    • 只被弱引用關聯的對象,不管當前內存是否足夠都會被GC;
    • 強度比軟引用更弱,經常使用於描述非必需對象。
  • 虛引用(PhantomReference)
    • 僅持有虛引用的對象,在任什麼時候候均可能被GC;
    • 經常使用於跟蹤對象被GC回收的活動;
    • 必須和引用隊列 (ReferenceQueue)聯合使用,當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。

a.引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。數組

然而在主流的Java虛擬機裏未選用引用計數算法來管理內存,主要緣由是它難以解決對象之間相互循環引用的問題,因此出現了另外一種對象存活斷定算法。緩存

b.可達性分析法:經過一系列被稱爲『GC Roots』的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。安全

可做爲GC Roots的對象:數據結構

  • 虛擬機棧中引用的對象,主要是指棧幀中的本地變量
  • 本地方法棧中Native方法引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象

須要注意的是,在可達性分析算法中被斷定不可達的對象還未真的判『死刑』,GC還會在執行finalize()方法的對象中進行一次篩選,若是對象能在finalize()中從新與引用鏈上的任何一個對象創建關聯,將被移除出「即將回收」的集合。併發


引申:有關方法區的GC,可分紅兩部分post

  • 廢棄常量與回收Java堆中的對象的GC很相似,即在任何地方都未被引用的常量會被GC。
  • 無用的類需知足如下三個條件纔會被GC:
    • 該類全部的實例都已被回收,即Java堆中不存在該類的任何實例;
    • 加載該類的ClassLoader已經被回收;
    • 該類對應的java.lang.Class對象沒在任何地方被引用,即沒法在任何地方經過反射訪問該類的方法。

2.垃圾收集算法性能

上一節介紹了JVM會回收哪些對象,接下來介紹JVM會如何回收掉這些對象。線程

a.分代收集算法

  • 根據對象存活週期的不一樣,將Java堆劃分爲新生代和老年代,並根據各個年代的特色採用最適當的收集算法。
    • 新生代:大批對象死去,只有少許存活。使用『複製算法』,只需複製少許存活對象便可。
    • 老年代:對象存活率高。使用『標記—清理算法』或者『標記—整理算法』,只需標記較少的回收對象便可。
  • 是當前商業虛擬機都採用的一種算法。

接下來依次介紹以上說起的另外三種算法。

b.複製算法

  • 把可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用盡後,把還存活着的對象『複製』到另一塊上面,再將這一塊內存空間一次清理掉。
  • 優勢:每次都是對整個半區進行內存回收,無需考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
  • 缺點:每次可以使用的內存縮小爲原來的一半,內存使用率低。

c.標記-清除算法

  • 首先『標記』出全部須要回收的對象,而後統一『清除』全部被標記的對象。
  • 是最基礎的收集算法。
  • 缺點:『標記』和『清除』過程的效率不高;空間碎片太多,『標記』『清除』以後會產生大量不連續的內存碎片,可能會致使後續須要分配較大對象時,因沒法找到足夠的連續內存而提早觸發另外一次GC,影響系統性能。

d.標記-整理算法

  • 首先『標記』出全部須要回收的對象,而後進行『整理』,使得存活的對象都向一端移動,最後直接清理掉端邊界之外的內存。
  • 優勢:即沒有浪費50%的空間,又不存在空間碎片問題,性價比較高。
  • 通常狀況下,老年代會選擇標記-整理算法。


3.HotSpot算法實現&垃圾回收器

接下來介紹如何在HotSpot虛擬機上實現對象存活斷定算法和垃圾收集算法,並保證虛擬機高效運行。

a.枚舉根節點

主流Java虛擬機使用的都是準確式GC,在執行系統停頓以後無需檢查全部執行上下文和全局的引用位置,而是經過一些辦法直接獲取到存放對象引用的地方,在HotSpot中是經過一組稱爲OopMap的數據結構來實現的,完成類加載後會計算出對象某偏移量上某類型數據、JIT編譯時會在特定的位置記錄棧和寄存器中是引用的位置。這樣GC在掃描時就可直接得知這些信息,並快速準確地完成GC Roots的枚舉。

b.安全點(Sefepoint)

上述「特定的位置」被稱爲安全點,即程序執行時並不是在全部地方都停頓執行GC,只在到達安全點時才暫停,下降GC的空間成本。

  • 安全點的選定標準:可以讓程序長時間執行的地方,如方法調用、循環跳轉、異常跳轉等具備指令序列複用的特徵。
  • 使全部線程在最近的安全點上再停頓的方案:
    • 搶先式中斷(Preemptive Suspension):無需代碼主動配合,在GC發生時把全部線程所有中斷,若線程中斷處不在安全點上就恢復線程,讓它「跑」到安全點上。如今幾乎沒有虛擬機實現採用搶先式中斷來暫停線程從而響應GC事件。
    • 主動式中斷(Voluntary Suspension):在GC要中斷線程時不直接對線程操做,而是設置一箇中斷標誌,讓各個線程在執行時主動輪詢它,當中斷標誌爲真時就本身中斷掛起。

c.安全區域(Safe Region)

安全點機制只能保證程序執行時,在不太長的時間內遇到可進入GC的安全點,但在程序不執行時(如線程處於Sleep或Blocked狀態)線程沒法響應JVM的中斷請求,此時就須要安全區域來解決。

  • 安全區域:引用關係不會發生變化的一段代碼片斷,在安全區域中的任意地方開始GC都是安全的,可看作是擴展的安全點。
  • 執行過程:當線程執行到安全區域中的代碼時就標識一下,若是這時JVM要發起GC就不用管被標識的線程;在線程要離開安全區域時檢查系統是否已經完成了根節點枚舉,若完成則線程能夠繼續執行,不然等待直到收到能夠安全離開安全區域的信號爲止。

到此只是簡單介紹了HotSpot如何發起內存回收,而具體的回收動做是由虛擬機所採用的GC收集器決定的,一般虛擬機中每每不止有一種GC收集器,下圖展現的是HotSpot虛擬機中存在的七種做用於不一樣分代(新生代、老年代)的收集器,其中被連線的兩個收集器表示能夠搭配使用。

如下是對比圖,來源於文章JVM(HotSpot) 垃圾收集器

並行(Parallel):多條垃圾收集線程並行工做,而用戶線程仍處於等待狀態。 併發(Concurrent):垃圾收集線程與用戶線程一段時間內同時工做,用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。


5.內存分配與回收策略

對象的內存分配廣義上是指在堆上分配,主要是在新生代的Eden區上,若是啓動了TLAB,將按線程優先在TLAB上分配,少數狀況下也可能會分配在老年代中。分配細節仍是取決於所使用的GC收集器組合以及虛擬機中與內存相關的參數的設置。如下介紹幾條廣泛的內存分配規則。

  • 對象優先在Eden分配:大多數狀況下對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時虛擬機將發起一次Minor GC。

新生代GC(Minor GC):發生在新生代的垃圾收集動做。較頻繁、回收速度也較快。 老年代GC(Major GC/Full GC):發生在老年代的垃圾收集動做。出現Major GC常常會伴隨至少一次的Minor GC。速度通常比Minor GC慢10倍以上。

  • 大對象直接進入老年代:對於須要大量連續內存空間的Java對象(如很長的字符串以及數組),若是大於虛擬機設定的-XX:PretenureSizeThreshold參數值將直接在老年代分配。這樣作的目的是避免在Eden區及兩個Survivor區之間發生大量的內存複製。

  • 長期存活的對象將進入老年代:虛擬機會給每一個對象定義一個年齡計數器,當對象在Eden出生並通過第一次Minor GC後仍存活且能被Survivor容納的話,將被移動到Survivor空間中並將對象年齡設爲1;當對象在Survivor區中每「熬過」一次Minor GC年齡就+1,直至增長到必定程度(默認爲15歲,可經過-XX: MaxTenuringThreshold設置)就會被晉升到老年代中。

  • 動態對象年齡斷定:爲了能更好地適應不一樣程序的內存情況,虛擬機並不要求必定要達到-XX: MaxTenuringThreshold設置值才能晉升到老年代,當Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,那麼年齡大於或等於該年齡的對象能夠直接進入老年代。

  • 空間分配擔保:在發生Minor GC以前虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,如果,說明可確保Minor GC是安全的,反之虛擬機會查看-XX:HandlePromotionFailure設置值是否容許擔保失敗;若容許,會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小;若大於,將嘗試進行一次Minor GC,若小於或者不容許擔保失敗,將改成進行一次Full GC。

解釋:當大量對象在MinorGC後仍然存活的狀況時,須要藉助老年代進行分配擔保,把Survivor沒法容納的對象直接進入老年代,但前提是老年代自己還有容納這些對象的剩餘空間,因爲在完成內存回收以前沒法預知實際存活對象,只好取以前每次回收晉升到老年代對象容量的平均大小值做爲經驗值,與老年代的剩餘空間進行比較,從而決定是否進行Full GC來讓老年代騰出更多空間。

相關文章
相關標籤/搜索