1. 程式人生 > >JVM垃圾回收與效能調優

JVM垃圾回收與效能調優

一、JVM記憶體結構

圖片來源於網路

 

1、方法區

 方法區用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料,描述為堆的一個邏輯部分,稱為非堆(HotSpot中也稱為永久代)。該區域包含執行時常量池Java8之後改為元空間(MetaSpace),直接分配實體記憶體。對這塊記憶體的GC條件很苛刻,基本認為不會進行。

2、堆

堆是被所有執行緒共享的一塊記憶體區域,是JVM管理的記憶體中最大的一塊,在虛擬機器啟動時建立。幾乎所有的物件例項以及陣列都要在堆分配記憶體。垃圾收集器管理的主要區域。

3、虛擬機器棧

也稱為執行緒本地棧,每個執行緒擁有自己獨立的一份,每呼叫一個方法都會建立一個棧幀,用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧和出棧的過程。

4、本地方法棧

為虛擬機器使用到的Native方法服務。與虛擬機器棧發揮的作用相似。有的虛擬機器(如HotSpot)把本地方法棧和虛擬機器棧合二為一了。

5、程式計數器

是當前執行緒所執行的位元組碼的行號指示器

二、什麼是垃圾

一般是指堆記憶體(方法區很少)中不會再使用到的物件,通俗講就是沒有引用指向它的物件。

引用的概念分類

  • 強引用:普遍存在。只要強引用還存在,垃圾收集器永遠不會回收掉被引用的物件。
  • 軟引用:還有用但並非必需的物件。對於軟引用關聯著的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中進行第二次回收。
  • 弱引用:非必需物件,比軟引用略弱。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。
  • 虛引用:也稱幽靈引用或者幻影引用,最弱的一種引用關係。

1、引用計數演算法

演算法:給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器值為0的物件就是不可能再被使用的。

優點:實現簡單,效率也高。

缺點:很難解決物件之間相互迴圈引用的問題

2、可達性分析演算法 

思路:通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GCRoots到這個物件不可達)時,則證明此物件是不可用的。

圖片來源於網路

如圖,物件object4、object5、object6雖然互相有關聯,但是它們到GCRoots是不可達的,所以它們將會被判定為可回收的物件。

在Java語言中可作為GC Roots的物件包括以下幾種: 

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件。
  • 方法區中類靜態屬性引用的物件。
  • 方法區中常量引用的物件。
  • 本地方法棧中JNI(即一般說的Native方法)引用的物件。

三、垃圾收集演算法

1、Mark-Sweep標記清除

如果所示:首先標記出所有需要回收的物件,在標記完成後統一回收被標記的物件。 

圖片來源於網路

缺點:

  • 效率問題,標記和清除兩個過程的效率都不高。
  • 空間問題,標記清除之後會產生大量不連續的記憶體碎片。當有大物件進來並且沒有足夠空間時,需要壓縮空間。

 2、Copying複製 

如圖所示:將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體使用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜問題,只要移動堆頂指標按順序分配記憶體即可。 

圖片來源於網路

優點:

  • 實現簡單
  • 執行高效

缺點:

  • 記憶體浪費

3、Mark-Compact標記整理

如圖所示: 讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

圖片來源於網路

四、垃圾收集器

1、CMS(Concurrent Mark Sweep)收集器

以獲取最短回收停頓時間為目標。

優點:併發收集、低停頓

缺點:對CPU資源敏感、無法處理浮動垃圾、產生大量空間碎片

2、G1(Garbag-First)收集器

優點:

  • 併發與並行
  • 分代收集
  • 空間整合
  • 可預測的停頓

更多請查閱https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/

3、Java平臺,標準版HotSpot虛擬機器垃圾收集調優指南

<<<<<<<<<<摘自Oracle官網>>>>>>>>>>

Java HotSpot VM包括三種不同型別的收集器,每種收集器具有不同的效能特徵。

  • 序列收集器使用單個執行緒來執行所有垃圾收集工作,這使得它相對有效,因為執行緒之間沒有通訊開銷。它最適合單處理器機器,因為它無法利用多處理器硬體,儘管它對於具有小資料集(最大約100 MB)的應用程式的多處理器非常有用。預設情況下,在某些硬體和作業系統配置上選擇序列收集器,或者可以使用該選項顯式啟用序列收集器-XX:+UseSerialGC。
  • 並行收集器(也稱為吞吐量收集器)並行執行次要收集,這可以顯著減少垃圾收集開銷。它適用於在多處理器或多執行緒硬體上執行的具有中型到大型資料集的應用程式。預設情況下,在某些硬體和作業系統配置上選擇並行收集器,或者可以使用該選項顯式啟用並行收集器-XX:+UseParallelGC。

並行壓縮是一種使並行收集器能夠並行執行主要集合的功能。如果沒有並行壓縮,主要集合將使用單個執行緒執行,這可能會顯著限制可伸縮性。如果-XX:+UseParallelGC已指定選項,則預設啟用並行壓縮。關閉它的選項是-XX:-UseParallelOldGC。

  • 大多數併發收集器同時執行大部分工作(例如,在應用程式仍在執行時)以防止垃圾收集暫停。它適用於具有中型到大型資料集的應用程式,其中響應時間比總吞吐量更重要,因為用於最小化暫停的技術會降低應用程式效能。Java HotSpot VM提供兩個主要併發收集器之間的選擇; 看到最常見的收藏家。使用該選項-XX:+UseConcMarkSweepGC可啟用CMS收集器或-XX:+UseG1GC啟用G1收集器。

選擇收集器

除非您的應用程式具有相當嚴格的暫停時間要求,否則首先執行您的應用程式並允許VM選擇收集器。如有必要,請調整堆大小以提高效能。如果效能仍不符合您的目標,請使用以下指南作為選擇收集器的起點。

  • 如果應用程式具有較小的資料集(最大約100 MB),那麼使用選項選擇序列收集器-XX:+UseSerialGC。
  • 如果應用程式將在單個處理器上執行且沒有暫停時間要求,則讓VM選擇收集器,或選擇帶有該選項的序列收集器-XX:+UseSerialGC。
  • 如果(a)峰值應用程式效能是第一優先順序並且(b)沒有暫停時間要求或1秒或更長的暫停是可接受的,則讓VM選擇收集器,或選擇並行收集器-XX:+UseParallelGC。
  • 如果響應時間比總吞吐量更重要,並且垃圾收集暫停必須保持短於大約1秒,則使用-XX:+UseConcMarkSweepGC或選擇併發收集器-XX:+UseG1GC。

這些指南僅提供了選擇收集器的起點,因為效能取決於堆的大小,應用程式維護的實時資料量以及可用處理器的數量和速度。暫停時間對這些因素特別敏感,因此前面提到的1秒閾值只是近似值:並行收集器在許多資料大小和硬體組合上將經歷超過1秒的暫停時間; 相反,併發收集器可能無法在某些組合上保持短於1秒的暫停。

如果推薦的收集器未達到所需效能,請首先嚐試調整堆和生成大小以滿足所需目標。如果效能仍然不足,那麼嘗試使用不同的收集器:使用併發收集器來減少暫停時間,並使用並行收集器來提高多處理器硬體的總吞吐量。

4、記憶體分配與回收策略

當前虛擬機器的垃圾收集都採用“分代收集”(Generational Collection)演算法:根據物件存活週期的不同將記憶體劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。在新生代中,每次垃圾收集時都發現有大批物件死去,只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或者“標記-整理”演算法來進行回收。

圖片來源於網路
  • 物件優先在Eden分配

  • 大物件直接進入老年代(-XX:PretenureSizeThreshold引數)

  • 長期存活的物件將進入老年代(-XX:MaxTenuringThreshold引數)

  • 動態物件年齡判定

  • 空間分配擔保

5、Java物件的分配

棧上分配

  • 執行緒私有小物件
  • 無逃逸(方法逃逸、執行緒逃逸):逃逸分析-XX:+DoEscapeAnalysis
  • 支援標量替換:-XX:+EliminateAllocations
  • 無需調整

執行緒本地分配:TLAB(Thread Local Allocation Buffer):-XX:+UseTLAB

  • 佔用eden,預設1%
  • 多執行緒的時候不用競爭eden就可以申請空間,提高效率
  • 小物件
  • 無需調整

老年代

  • 大物件

Eden

五、JVM常用引數設定

-:標準引數,所有JVM都應該支援

-X:非標,每個JVM實現都不同

-XX:不穩定引數,下一個版本可能會取消

圖片來源於網路

常用設定套路

1、-Xms和-Xmx設定相近或相等:避免Java堆記憶體自動擴容時帶來的效能消耗 。

2、-Xss:設定小,執行的執行緒可以多一些。設定大,方法的呼叫可以深一些。

六、案例分析

1、Java物件的分配

當關閉逃逸分析,關閉標量替換,關閉執行緒本地程式時

改下JVM引數

 

 

2、用Runtime類“大致”計算記憶體情況

3、記憶體溢位分析

 

 

可以看到,byte[]佔了大部分記憶體。

4、執行緒棧大小測試 

 

七、常用工具

1、JConsole:Java監視與管理控制檯

2、VisualVM:多合一故障處理工具

無監控,不調優!!!