1. 程式人生 > 實用技巧 >Java虛擬機器JVM效能調優實戰 JVM原理和調優

Java虛擬機器JVM效能調優實戰 JVM原理和調優

16年的時候花了一些時間整理了一些關於jvm的介紹文章,到現在回顧起來還是一些還沒有補充全面,其中就包括如何利用工具來監控調優前後的效能變化。工具做為圖形化介面來展示更能直觀的發現問題,另一方面一些耗費效能的分析(dump檔案分析)一般也不會在生產直接分析,往往dump下來的檔案達1G左右,人工分析效率較低,因此利用工具來分析jvm相關問題,長長可以到達事半功倍的效果來。

jvm監控分析工具一般分為兩類,一種是jdk自帶的工具,一種是第三方的分析工具。jdk自帶工具一般在jdk bin目錄下面,以exe的形式直接點選就可以使用,其中包含分析工具已經很強大,幾乎涉及了方方面面,但是我們最常使用的只有兩款:jconsole.exe和jvisualvm.exe;第三方的分析工具有很多,各自的側重點不同,比較有代表性的:MAT(Memory Analyzer Tool)、GChisto等。

對於大型 JAVA 應用程式來說,再精細的測試也難以堵住所有的漏洞,即便我們在測試階段進行了大量卓有成效的工作,很多問題還是會在生產環境下暴露出來,並且很難在測試環境中進行重現。JVM 能夠記錄下問題發生時系統的部分執行狀態,並將其儲存在堆轉儲 (Heap Dump) 檔案中,從而為我們分析和診斷問題提供了重要的依據。其中VisualVM和MAT是dump檔案的分析利器。

jdk自帶的工具

jconsole

Jconsole(Java Monitoring and Management Console)是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中記憶體,執行緒和類等的監控,是一個基於JMX(java management extensions)的GUI效能監測工具。jconsole使用jvm的擴充套件機制獲取並展示虛擬機器中執行的應用程式的效能和資源消耗等資訊。

直接在jdk/bin目錄下點選jconsole.exe即可啟動,介面如下:

在彈出的框中可以選擇本機的監控本機的java應用,也可以選擇遠端的java服務來監控,如果監控遠端服務需要在tomcat啟動指令碼中新增如下程式碼:

  1. -Dcom.sun.management.jmxremote.port=6969
  2. -Dcom.sun.management.jmxremote.ssl=false
  3. -Dcom.sun.management.jmxremote.authenticate=false

連線進去之後,就可以看到jconsole概覽圖和主要的功能:概述、記憶體、執行緒、類、VM、MBeans

  • 概述,以圖表的方式顯示出堆記憶體使用量,活動執行緒數,已載入的類,CUP佔用率的折線圖,可以非常清晰的觀察在程式執行過程中的變動情況。

  • 記憶體,主要展示了記憶體的使用情況,同時可以檢視堆和非堆記憶體的變化值對比,也可以點選執行GC來處罰GC的執行

  • 執行緒,主介面展示執行緒數的活動數和峰值,同時點選左下方執行緒可以檢視執行緒的詳細資訊,比如執行緒的狀態是什麼,堆疊內容等,同時也可以點選“檢測死鎖”來檢查執行緒之間是否有死鎖的情況。

  • 類,主要展示已載入類的相關資訊。

  • VM 概要,展示JVM所有資訊總覽,包括基本資訊、執行緒相關、堆相關、作業系統、VM引數等。

  • Mbean,檢視Mbean的屬性,方法等。

VisualVM

簡介

VisualVM 是一個工具,它提供了一個可視介面,用於檢視 Java 虛擬機器 (Java Virtual Machine, JVM) 上執行的基於 Java 技術的應用程式(Java 應用程式)的詳細資訊。VisualVM 對 Java Development Kit (JDK) 工具所檢索的 JVM 軟體相關資料進行組織,並通過一種使您可以快速檢視有關多個 Java 應用程式的資料的方式提供該資訊。您可以檢視本地應用程式以及遠端主機上執行的應用程式的相關資料。此外,還可以捕獲有關 JVM 軟體例項的資料,並將該資料儲存到本地系統,以供後期檢視或與其他使用者共享。

VisualVM 是javajdk自帶的最牛逼的調優工具了吧,也是我平時使用最多調優工具,幾乎涉及了jvm調優的方方面面。同樣是在jdk/bin目錄下面雙擊jvisualvm.exe既可使用,啟動起來後和jconsole 一樣同樣可以選擇本地和遠端,如果需要監控遠端同樣需要配置相關引數,主介面如下;

VisualVM可以根據需要安裝不同的外掛,每個外掛的關注點都不同,有的主要監控GC,有的主要監控記憶體,有的監控執行緒等。

如何安裝:

1、從主選單中選擇“工具”>“外掛”。

2、在“可用外掛”標籤中,選中該外掛的“安裝”複選框。單擊“安裝”。

3、逐步完成外掛安裝程式。

我這裡以 Eclipse(pid 22296)為例,雙擊後直接展開,主介面展示了系統和jvm兩大塊內容,點選右下方jvm引數和系統屬性可以參考詳細的引數資訊.

因為VisualVM的外掛太多,我這裡主要介紹三個我主要使用幾個:監控、執行緒、Visual GC

監控的主頁其實也就是,cpu、記憶體、類、執行緒的圖表

執行緒和jconsole功能沒有太大的區別

Visual GC 是常常使用的一個功能,可以明顯的看到年輕代、老年代的記憶體變化,以及gc頻率、gc的時間等。

以上的功能其實jconsole幾乎也有,VisualVM更全面更直觀一些,另外VisualVM非常多的其它功能,可以分析dump的記憶體快照,dump出來的執行緒快照並且進行分析等,還有其它很多的外掛大家可以去探索

第三方調優工具

MAT

MAT是什麼?

MAT(Memory Analyzer Tool),一個基於Eclipse的記憶體分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查詢記憶體洩漏和減少記憶體消耗。使用記憶體分析工具從眾多的物件中進行分析,快速的計算出在記憶體中物件的佔用大小,看看是誰阻止了垃圾收集器的回收工作,並可以通過報表直觀的檢視到可能造成這種結果的物件。

通常記憶體洩露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲檔案分析工具,你只需要輕輕點選一下滑鼠就可以生成一個專業的分析報告。和其他記憶體洩露分析工具相比,MAT 的使用非常容易,基本可以實現一鍵到位,即使是新手也能夠很快上手使用。

MAT以eclipse 外掛的形式來安裝,具體的安裝過程就不在描述了,可以利用visualvm或者是 jmap命令生產堆檔案,匯入eclipse mat中生成分析報告:

生產這會報表的同時也會在dump檔案的同級目錄下生成三份(dump_Top_Consumers.zip、dump_Leak_Suspects.zip、dump_Top_Components.zip)分析結果的html檔案,方便傳送給相關同事來檢視。

需要關注的是下面的Actions、Reports、Step by Step區域:

  • Histogram:列出記憶體中的物件,物件的個數以及大小,支援正則表示式查詢,也可以計算出該類所有物件的retained size

  • Dominator Tree:列出最大的物件以及其依賴存活的Object (大小是以Retained Heap為標準排序的)

  • Top Consumers : 通過圖形列出最大的object

  • duplicate classes :檢測由多個類裝載器載入的類

  • Leak Suspects :記憶體洩漏分析

  • Top Components: 列出大於總堆數的百分之1的報表。

  • Component Report:分析物件屬於同一個包或者被同一個類載入器載入

以上只是一個初級的介紹,mat還有更強大的使用,比如對比堆記憶體,在生產環境中往往為了定位問題,每隔幾分鐘dump出一下記憶體快照,隨後在對比不同時間的堆記憶體的變化來發現問題。

GChisto

GChisto是一款專業分析gc日誌的工具,可以通過gc日誌來分析:Minor GC、full gc的時間、頻率等等,通過列表、報表、圖表等不同的形式來反應gc的情況。雖然介面略顯粗糙,但是功能還是不錯的。

配置好本地的jdk環境之後,雙擊GChisto.jar,在彈出的輸入框中點選 add 選擇gc.log日誌

  • GC Pause Stats:可以檢視GC 的次數、GC的時間、GC的開銷、最大GC時間和最小GC時間等,以及相應的柱狀圖

  • GC Pause Distribution:檢視GC停頓的詳細分佈,x軸表示垃圾收集停頓時間,y軸表示是停頓次數。

  • GC Timeline:顯示整個時間線上的垃圾收集

不過這款工具已經不再維護,不能識別最新jdk的日誌檔案。

gcviewer

GCViewer也是一款分析小工具,用於視覺化檢視由Sun / Oracle, IBM, HP 和 BEA Java 虛擬機器產生的垃圾收集器的日誌,gcviewer個人感覺顯示 的介面比較亂沒有GChisto更專業一些。

一、什麼是JVM

  JVM是Java Virtual Machine(Java虛擬機器)的縮寫,JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。

  Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機器是實現這一特點的關鍵。一般的高階語言如果要在不同的平臺上執行,至少需要編譯成不同的目的碼。而引入Java語言虛擬機器後,Java語言在不同平臺上執行時不需要重新編譯。Java語言使用Java虛擬機器遮蔽了與具體平臺相關的資訊,使得Java語言編譯程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。Java虛擬機器在執行位元組碼時,把位元組碼解釋成具體平臺上的機器指令執行。這就是Java的能夠“一次編譯,到處執行”的原因。

  從Java平臺的邏輯結構上來看,我們可以從下圖來了解JVM:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105051027?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  從上圖能清晰看到Java平臺包含的各個邏輯模組,也能瞭解到JDK與JRE的區別,對於JVM自身的物理結構,我們可以從下圖鳥瞰一下:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105116962?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

二、JAVA程式碼編譯和執行過程

  Java程式碼編譯是由Java原始碼編譯器來完成,流程圖如下所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105132577?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  Java位元組碼的執行是由JVM執行引擎來完成,流程圖如下所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105150514?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  Java程式碼編譯和執行的整個過程包含了以下三個重要的機制:

  • Java原始碼編譯機制

  • 類載入機制

  • 類執行機制

  1. Java原始碼編譯機制

  Java 原始碼編譯由以下三個過程組成:

  • 分析和輸入到符號表

  • 註解處理

  • 語義分析和生成class檔案

  流程圖如下所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105851463?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  最後生成的class檔案由以下部分組成:

  • 結構資訊。包括class檔案格式版本號及各部分的數量與大小的資訊

  • 元資料。對應於Java原始碼中宣告與常量的資訊。包含類/繼承的超類/實現的介面的宣告資訊、域與方法宣告資訊和常量池

  • 方法資訊。對應Java原始碼中語句和表示式對應的資訊。包含位元組碼、異常處理器表、求值棧與區域性變數區大小、求值棧的型別記錄、除錯符號資訊

  1. 類載入機制

  負責載入$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由下圖來描述:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110146601?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

1). Bootstrap ClassLoader

  負責載入$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

2). Extension ClassLoader

  負責載入java平臺中擴充套件功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

3). App ClassLoader

  負責記載classpath中指定的jar包及目錄中class

4). Custom ClassLoader

  屬於應用程式根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已載入就視為已載入此類,保證此類只所有ClassLoader載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。

  1. 類執行機制

  JVM是基於棧的體系結構來執行class位元組碼的。執行緒建立後,都會產生程式計數器(PC)和棧(Stack),程式計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次呼叫,而棧幀又是有區域性變數區和運算元棧兩部分組成,區域性變數區用於存放方法中的區域性變數和引數,運算元棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916105916917?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

三、JVM記憶體管理和垃圾回收

  1. JVM記憶體組成結構

  JVM棧由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110312414?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

1). 堆

  所有通過new建立的物件的記憶體都在堆中分配,堆的大小可以通過-Xmx和-Xms來控制。堆被劃分為新生代和舊生代,新生代又被進一步劃分為Eden和Survivor區,最後Survivor由From Space和To Space組成,結構圖如下所示:

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110332231?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
  • 新生代。新建的物件都是用新生代分配記憶體,Eden空間不足的時候,會把存活的物件轉移到Survivor中,新生代大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例

  • 舊生代。用於存放新生代中經過多次垃圾回收仍然存活的物件

  • 持久代(Permanent Space)實現方法區,主要存放所有已載入的類資訊,方法資訊,常量池等等。可通過-XX:PermSize和-XX:MaxPermSize來指定持久帶初始化值和最大值。Permanent Space並不等同於方法區,只不過是Hotspot JVM用Permanent Space來實現方法區而已,有些虛擬機器沒有Permanent Space而用其他機制來實現方法區。

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110347411?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
-Xmx:最大堆記憶體,如:-Xmx512m

-Xms:初始時堆記憶體,如:-Xms256m

-XX:MaxNewSize:最大年輕區記憶體

-XX:NewSize:初始時年輕區記憶體.通常為 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間為 = Eden + 1 個 Survivor,即 90%

-XX:MaxPermSize:最大持久帶記憶體

-XX:PermSize:初始時持久帶記憶體

-XX:+PrintGCDetails。列印 GC 資訊

 -XX:NewRatio 新生代與老年代的比例,如 –XX:NewRatio=2,則新生代佔整個堆空間的1/3,老年代佔2/3

 -XX:SurvivorRatio 新生代中 Eden 與 Survivor 的比值。預設值為 8。即 Eden 佔新生代空間的 8/10,另外兩個 Survivor 各佔 1/10
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2). 棧

  每個執行緒執行每個方法的時候都會在棧中申請一個棧幀,每個棧幀包括區域性變數區和運算元棧,用於存放此次方法呼叫過程中的臨時變數、引數和中間結果。

  -xss:設定每個執行緒的堆疊大小. JDK1.5+ 每個執行緒堆疊大小為 1M,一般來說如果棧不是很深的話, 1M 是絕對夠用了的。

3). 本地方法棧

  用於支援native方法的執行,儲存了每個native方法呼叫的狀態

4). 方法區

  存放了要載入的類資訊、靜態變數、final型別的常量、屬性和方法資訊。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值

  1. 垃圾回收

1). 引用計數(Reference Counting)

  比較古老的回收演算法。原理是此物件有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數為0的物件。此演算法最致命的是無法處理迴圈引用的問題。

2). 標記-清除(Mark-Sweep)

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110459821?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  此演算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的物件,第二階段遍歷整個堆,把未標記的物件清除。此演算法需要暫停整個應用,同時,會產生記憶體碎片。

3). 複製(Copying)

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110509959?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  此演算法把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的物件複製到另外一個區域中。演算法每次只處理正在使用中的物件,因此複製成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現“碎片”問題。當然,此演算法的缺點也是很明顯的,就是需要兩倍記憶體空間。

4). 標記-整理(Mark-Compact)

![這裡寫圖片描述](https://img-blog.csdn.net/20170916110737722?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuMTAyMTg3MzkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

  此演算法結合了“標記-清除”和“複製”兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,把清除未標記物件並且把存活物件“壓縮”到堆的其中一塊,按順序排放。此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。

  1. JVM分別對新生代和舊生代採用不同的垃圾回收機制
新生代的GC:

  新生代通常存活時間較短,因此基於Copying演算法來進行回收,所謂Copying演算法就是掃描出存活的物件,並複製到一塊新的完全未使用的空間中,對應於新生代,就是在Eden和From Space或To Space之間copy。新生代採用空閒指標的方式來控制GC觸發,指標保持最後一個分配的物件在新生代區間的位置,當有新的物件要分配記憶體時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配物件時,物件會逐漸從eden到survivor,最後到舊生代。

  在執行機制上JVM提供了序列GC(Serial GC)、並行回收GC(Parallel Scavenge)和並行GC(ParNew)

1). 序列GC

  在整個掃描和複製過程採用單執行緒的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client級別預設的GC方式,可以通過-XX:+UseSerialGC來強制指定

2). 並行回收GC

  在整個掃描和複製過程採用多執行緒的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別預設採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定執行緒數

3). 並行GC

與舊生代的併發GC配合使用

舊生代的GC:

  舊生代與新生代不同,物件存活的時間比較長,比較穩定,因此採用標記(Mark)演算法來進行回收,所謂標記就是掃描出存活的物件,然後再進行回收未被標記的物件,回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之就是要減少記憶體碎片帶來的效率損耗。在執行機制上JVM提供了序列GC(Serial MSC)、並行GC(parallel MSC)和併發GC(CMS),具體演算法細節還有待進一步深入研究。

以上各種GC機制是需要組合使用的,指定方式由下表所示:

指定方式新生代GC方式舊生代GC方式
-XX:+UseSerialGC 序列GC 序列GC
-XX:+UseParallelGC 並行回收GC 並行GC
-XX:+UseConeMarkSweepGC 並行GC 併發GC
-XX:+UseParNewGC 並行GC 序列GC
-XX:+UseParallelOldGC 並行回收GC 並行GC
-XX:+ UseConeMarkSweepGC && -XX:+UseParNewGC 序列GC 併發GC
不支援的組合 -XX:+UseParNewGC -XX:+UseParallelOldGC && -XX:+UseParNewGC -XX:+UseSerialGC -XX:+UseParNewGC -XX:+UseParallelOldGC && -XX:+UseParNewGC -XX:+UseSerialGC

四、JVM記憶體調優

  首先需要注意的是在對JVM記憶體調優的時候不能只看作業系統級別Java程序所佔用的記憶體,這個數值不能準確的反應堆記憶體的真實佔用情況,因為GC過後這個值是不會變化的,因此記憶體調優的時候要更多地使用JDK提供的記憶體檢視工具,比如JConsole和Java VisualVM。

  對JVM記憶體的系統級的調優主要的目的是減少GC的頻率和Full GC的次數,過多的GC和Full GC是會佔用很多的系統資源(主要是CPU),影響系統的吞吐量。特別要關注Full GC,因為它會對整個堆進行整理,導致Full GC一般由於以下幾種情況:

  1. 舊生代空間不足

  調優時儘量讓物件在新生代GC時被回收、讓物件在新生代多存活一段時間和不要建立過大的物件及陣列避免直接在舊生代建立物件

  1. Pemanet Generation空間不足

  增大Perm Gen空間,避免太多靜態物件

  統計得到的GC後晉升到舊生代的平均大小大於舊生代剩餘空間

  控制好新生代和舊生代的比例

  1. System.gc()被顯示呼叫

  垃圾回收不要手動觸發,儘量依靠JVM自身的機制

  1. 調優手段主要是通過控制堆記憶體的各個部分的比例和GC策略來實現,下面來看看各部分比例不良設定會導致什麼後果

1). 新生代設定過小

  一是新生代GC次數非常頻繁,增大系統消耗;二是導致大物件直接進入舊生代,佔據了舊生代剩餘空間,誘發Full GC

2). 新生代設定過大

  一是新生代設定過大會導致舊生代過小(堆總量一定),從而誘發Full GC;二是新生代GC耗時大幅度增加

  一般說來新生代佔整個堆1/3比較合適

3). Survivor設定過小

  導致物件從eden直接到達舊生代,降低了在新生代的存活時間

4). Survivor設定過大

  導致eden過小,增加了GC頻率

  另外,通過-XX:MaxTenuringThreshold=n來控制新生代存活時間,儘量讓物件在新生代被回收

  1. 由記憶體管理和垃圾回收可知新生代和舊生代都有多種GC策略和組合搭配,選擇這些策略對於我們這些開發人員是個難題,JVM提供兩種較為簡單的GC策略的設定方式

1). 吞吐量優先

  JVM以吞吐量為指標,自行選擇相應的GC策略及控制新生代與舊生代的大小比例,來達到吞吐量指標。這個值可由-XX:GCTimeRatio=n來設定

2). 暫停時間優先

  JVM以暫停時間為指標,自行選擇相應的GC策略及控制新生代與舊生代的大小比例,儘量保證每次GC造成的應用停止時間都在指定的數值範圍內完成。這個值可由-XX:MaxGCPauseRatio=n來設定

五、最後彙總一下JVM常見配置