垃圾收集器與內存分配策略

  通過半個多世紀的發展、目前內存的動態分配與內存回收技術已經至關成熟,但做爲程序猿仍是得了解GC和內存分配。當須要排查各類內存溢出、內存泄漏、當垃圾收集成爲系統達到更高併發量的瓶頸時,就須要對內存的動態分配與內存回收技術實施必要的監控和調節。算法

  本文講敘了內存中垃圾的收集及內存分配策略。相比較而言,垃圾收集更難一些。本文將介紹幾種常見的垃圾收集器及經常使用垃圾收集算法。垃圾收集算法是基於判斷對象在內存中是否死亡,只有判斷肯定出對象已經死亡,才能採起不一樣的方式進行收集,實現內存的回收。
數組

 

判斷對象死亡經常使用方法

  引用計數算法(Reference Counting):給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就+1;當引用失效時,計數器值就-1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。實現簡單、斷定效率高很難解決對象之間的循環引用問題,目前主流Java虛擬機裏沒有選用計數算法來管理內存。安全

  可達性分析算法(Reachability Analysis):經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可引用的。即便是不可達的對象,也並不是是「非死不可」的,只是暫時處於「緩刑」階段,真正宣告死亡,至少仍是經歷再次標記過程:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,則將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種狀況都視爲「沒有必要執行」。在Java語言中,可做爲GC Roots的對象有四種:(1)虛擬機棧中引用的對象;(2)方法區中類靜態屬性引用的對象;(3)方法區中常量引用的對象;(4)本地方法棧中JNI引用的對象。多線程

  判斷對象是否存活與「引用」相關。引用可分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)四種,其引用強度依次減弱。併發

 

垃圾收集算法

  標記—清除算法(Mark-Sweep):分爲「標記」和「清除」兩個階段,首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。主要兩處不足:一個是效率問題,標記和清除兩個過程的效率都不高;二是空間問題,標記清除以後會產生大量不連續的內存碎片。高併發

  複製算法(Copying):將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊,當這一塊用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次性清理掉。可根據實際需求將兩塊內存按不一樣比例劃分。如今商業虛擬機都採用這種收集算法來回收新生代。在對象存活率較高時,就要進行較多的複製操做,效率會變低。性能

  標記—整理算法(Mark-Compact):標記的過程與「標記—清除」算法同樣,只是在整理階段,讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的的內存。spa

  分代收集算法(Generational Collection):根據對象存活週期的不一樣將內存劃分爲幾塊,通常將Java堆分爲新生代和老年代,再根據各個年代的特色採用最適當的收集算法。線程

 

垃圾收集器

垃圾收集器是內存回收的具體實現。基於JDK 1.7 Update 14以後的HotSpot虛擬機所包含的收集器以下圖所示:對象

  Serial收集器:單線程收集器,只使用一個CPU或一條線程去完成垃圾收集工做,在垃圾收集時,必須暫停其餘全部工做線程,直接收集結束。對於運行在Client模式下的虛擬機來講是一個很好的選擇

  ParNew收集器:Serial收集器的多線程版本。運行在Server模式下的虛擬機中首先的新生代收集器。除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工做。

  Parallel Scavenge收集器:也稱爲「吞吐量優先」收集器,關注點是達到一個可控制的吞吐量(Throughput)。吞吐量=運行用戶代碼時間/(運行代碼時間+垃圾收集時間)。自適應調節策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。GC自適應調節策略(GC Ergonomics)是指虛擬機根據當前系統的運行狀況收集性能監控信息,動態調整參數-XX:+UseAdaptiveSizePolicy以提供最合適的停頓時間或者最大的吞吐量。

  Serial Old 收集器單線程收集器,使用「標記—整理」算法。主要意義是在於給Client模式下的虛擬機使用。

  Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多線程和「標記—整理」算法,在注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。

  CMS收集器(Concurrent Mark Sweep):以獲取最短回收停頓時間爲目標的收集器。整個過程可分爲初始標記(CMS initial mark)、併發標記(CMS concurrent mark)、從新標記(CMS remark)、併發清除(CMS concurrent sweep)4個步驟。整個過程當中耗時最長的是併發標記和併發清除過程,但它們均可以與用戶線程一塊兒工做。整體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。有3個明顯的缺點:(1)CMS收集器對CPU資源很是敏感;(2)CMS收集器沒法處理浮動垃圾(Floating Garbage);(3)收集結束時會有大量的空間碎片產生。

  G1收集器(Garbage-First):當今收集器技術發展最前沿成果之一。在G1以前的收集器收集範圍是整個新生代或者老年代,G1再也不是這樣。G1將整個堆劃分爲多個大小相等的獨立區域(Region),G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region,從而保證了G1收集器在有限的時間內能夠獲取儘量高的收集效率。運行分爲4個步驟:初始標記(Initial Marking)、併發標記(Concurrent Marking)、最終標記(Final Marking)和篩選回收(Live Data Counting and Evacuation)。具有以下特色:並行與併發、分代收集、空間整合、可預測的停頓。

 

內存分配與回收策略

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

  大對象直接進行老年代:所謂大對象是指須要大量連續內存空間的Java對象,很長的字符串以及數組就是最典型的大對象。

  長期存活的對象將進入老年代:虛擬機給每一個對象定義一個對象年齡(Age)計數器。若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor空間中,而且對象年齡設爲1。對象在Survivor區中每「熬過」一次Minor GC,年齡就增長1歲,當年齡達到必定程度時,就會被晉升到老年代中。晉升閾值能夠經過參數設置。

  動態對象年齡斷定:爲了能更好地適應不一樣的內存情況,虛擬機並非永遠地要求對象的的年齡必須達到MaxTenuringThreshold才能晉升老年代。若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可直接進入老年代。

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

相關文章
相關標籤/搜索