1. 程式人生 > >Java效能系一(JVM、垃圾回收、記憶體調優、常見引數)

Java效能系一(JVM、垃圾回收、記憶體調優、常見引數)

一、什麼是JVM

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

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

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

wKioL1Xs9MLDQRHuAAHLlb4NrvA724.gif

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

wKiom1Xs8uyjeKCKAADr2P7KwZ4547.gif

 

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

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

wKiom1Xs84Kz3tLNAAD0t06TroI238.gif

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

wKiom1Xs85uxOF5mAAFJNP-fzhg385.gif

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

  • Java原始碼編譯機制

  • 類載入機制

  • 類執行機制

Java原始碼編譯機制

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

  • 分析和輸入到符號表

  • 註解處理

  • 語義分析和生成class檔案

流程圖如下所示:

wKiom1Xs9FPAesSjAAAs7C8hkWs833.gif

 

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

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

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

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

類載入機制

JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由下圖來描述:

wKiom1Xs9HazvE-rAALSsJvKx8U794.gif

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載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。

 

類執行機制

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

wKiom1Xs9MHg3bYVAAAmhLAOiik099.gif

 

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

JVM記憶體組成結構

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

wKiom1Xs9aWhN3OEAABJZEwuJ-c836.gif

1)堆

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

wKiom1Xs9ceyZWP0AAB-_k0IkUo882.gif

  • 新生代。新建的物件都是用新生代分配記憶體,Eden空間不足的時候,會把存活的物件轉移到Survivor中,新生代大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例

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

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

wKiom1Xs-fLx4MWrAAEPniOGR34183.jpg

  • -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

 

2)棧

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

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

3)本地方法棧

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

4)方法區

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

 

垃圾回收按照基本回收策略分

引用計數(Reference Counting):

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

 

標記-清除(Mark-Sweep):

wKiom1Xs_uqCNFJgAACkJWszH9Y986.jpg

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

 

複製(Copying):

wKioL1XtAUXT2TQ6AACmPxPQHwc598.jpg

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

 

標記-整理(Mark-Compact):

wKioL1XtAXfDO_2zAACkJWszH9Y289.jpg

 

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

 

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

不支援的組合

1、-XX:+UseParNewGC -XX:+UseParallelOldGC

2、-XX:+UseParNewGC -XX:+UseSerialGC

 

四、JVM記憶體調優

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

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

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

Pemanet Generation空間不足
    增大Perm Gen空間,避免太多靜態物件 

    統計得到的GC後晉升到舊生代的平均大小大於舊生代剩餘空間
    控制好新生代和舊生代的比例 

System.gc()被顯示呼叫
    垃圾回收不要手動觸發,儘量依靠JVM自身的機制 

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

1)新生代設定過小

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

2)新生代設定過大

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

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

3)Survivor設定過小

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

4)Survivor設定過大

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

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

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

1)吞吐量優先

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

2)暫停時間優先

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

 

最後彙總一下JVM常見配置

堆設定

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:設定年輕代大小

-XX:NewRatio=n:設定年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代佔整個年輕代年老代和的1/4

-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5

-XX:MaxPermSize=n:設定持久代大小

收集器設定

-XX:+UseSerialGC:設定序列收集器

-XX:+UseParallelGC:設定並行收集器

-XX:+UseParalledlOldGC:設定並行年老代收集器

-XX:+UseConcMarkSweepGC:設定併發收集器

垃圾回收統計資訊

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

並行收集器設定

-XX:ParallelGCThreads=n:設定並行收集器收集時使用的CPU數。並行收集執行緒數。

-XX:MaxGCPauseMillis=n:設定並行收集最大暫停時間

-XX:GCTimeRatio=n:設定垃圾回收時間佔程式執行時間的百分比。公式為1/(1+n)

併發收集器設定

-XX:+CMSIncrementalMode:設定為增量模式。適用於單CPU情況。

-XX:ParallelGCThreads=n:設定併發收集器年輕代收集方式為並行收集時,使用的CPU數。並行收集執行緒數。

 

 

我自己生產環境配置如下,tomcat是多例項的

1

CATALINA_OPTS="-Xms1024m -Xmx1024m -XX:NewRatio=4 -XX:PermSize=192m -XX:MaxPermSize=192m -Xss256k -XX:SurvivorRatio=4"

連線池

1

2

3

4

5

6

7

8

9

10

11

<Connector port="8080" protocol="HTTP/1.1"

               maxHttpHeaderSize="8192"

               maxThreads="1000"

               minSpareThreads="100"

               maxSpareThreads="1000"

               enableLookups="false"

               connectionTimeout="10000"

               URIEncoding="utf-8"

               acceptCount="1000"

               redirectPort="8443"

               disableUploadTimeout="true"/>


調優續:

 

要了解Java垃圾收集機制,先理解JVM記憶體模式是非常重要的。今天我們將會了解JVM記憶體的各個部分、如何監控以及垃圾收集調優。

Java(JVM)記憶體模型

正如你從上面的圖片看到的,JVM記憶體被分成多個獨立的部分。廣泛地說,JVM堆記憶體被分為兩部分——年輕代(Young Generation)和老年代(Old Generation)。

年輕代

年輕代是所有新物件產生的地方。當年輕代記憶體空間被用完時,就會觸發垃圾回收。這個垃圾回收叫做Minor GC。年輕代被分為3個部分——Enden區和兩個Survivor區。

年輕代空間的要點:

  • 大多數新建的物件都位於Eden區。
  • 當Eden區被物件填滿時,就會執行Minor GC。並把所有存活下來的物件轉移到其中一個survivor區。
  • Minor GC同樣會檢查存活下來的物件,並把它們轉移到另一個survivor區。這樣在一段時間內,總會有一個空的survivor區。
  • 經過多次GC週期後,仍然存活下來的物件會被轉移到年老代記憶體空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。

年老代

年老代記憶體裡包含了長期存活的物件和經過多次Minor GC後依然存活下來的物件。通常會在老年代記憶體被佔滿時進行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC會花費更多的時間。

Stop the World事件

所有的垃圾收集都是“Stop the World”事件,因為所有的應用執行緒都會停下來直到操作完成(所以叫“Stop the World”)。

因為年輕代裡的物件都是一些臨時(short-lived )物件,執行Minor GC非常快,所以應用不會受到(“Stop the World”)影響。

由於Major GC會檢查所有存活的物件,因此會花費更長的時間。應該儘量減少Major GC。因為Major GC會在垃圾回收期間讓你的應用反應遲鈍,所以如果你有一個需要快速響應的應用發生多次Major GC,你會看到超時錯誤。

垃圾回收時間取決於垃圾回收策略。這就是為什麼有必要去監控垃圾收集和對垃圾收集進行調優。從而避免要求快速響應的應用出現超時錯誤。

永久代

永久代或者“Perm Gen”包含了JVM需要的應用元資料,這些元資料描述了在應用裡使用的類和方法。注意,永久代不是Java堆記憶體的一部分。

永久代存放JVM執行時使用的類。永久代同樣包含了Java SE庫的類和方法。永久代的物件在full GC時進行垃圾收集。

方法區

方法區是永久代空間的一部分,並用來儲存型別資訊(執行時常量和靜態變數)和方法程式碼和建構函式程式碼。

記憶體池

如果JVM實現支援,JVM記憶體管理會為建立記憶體池,用來為不變物件建立物件池。字串池就是記憶體池型別的一個很好的例子。記憶體池可以屬於堆或者永久代,這取決於JVM記憶體管理的實現。

執行時常量池

執行時常量池是每個類常量池的執行時代表。它包含了類的執行時常量和靜態方法。執行時常量池是方法區的一部分。

Java棧記憶體

Java棧記憶體用於執行執行緒。它們包含了方法裡的臨時資料、堆裡其它物件引用的特定資料。你可以閱讀棧記憶體和堆記憶體的區別

Java 堆記憶體開關

Java提供了大量的記憶體開關(引數),我們可以用它來設定記憶體大小和它們的比例。下面是一些常用的開關:

VM 開關 VM 開關描述
-Xms 設定JVM啟動時堆的初始化大小。
-Xmx 設定堆最大值。
-Xmn 設定年輕代的空間大小,剩下的為老年代的空間大小。
-XX:PermGen 設定永久代記憶體的初始化大小。
-XX:MaxPermGen 設定永久代的最大值。
-XX:SurvivorRatio 提供Eden區和survivor區的空間比例。比如,如果年輕代的大小為10m並且VM開關是-XX:SurvivorRatio=2,那麼將會保留5m記憶體給Eden區和每個Survivor區分配2.5m記憶體。預設比例是8。
-XX:NewRatio 提供年老代和年輕代的比例大小。預設值是2。

大多數時候,上面的選項已經足夠使用了。但是如果你還想了解其他的選項,那麼請檢視JVM選項官方網頁

Java垃圾回收

Java垃圾回收會找出沒用的物件,把它從記憶體中移除並釋放出記憶體給以後建立的物件使用。Java程式語言中的一個最大優點是自動垃圾回收,不像其他的程式語言那樣需要手動分配和釋放記憶體,比如C語言。

垃圾收集器是一個後臺執行程式。它管理著記憶體中的所有物件並找出沒被引用的物件。所有的這些未引用的物件都會被刪除,回收它們的空間並分配給其他物件。

一個基本的垃圾回收過程涉及三個步驟:

  1. 標記:這是第一步。在這一步,垃圾收集器會找出哪些物件正在使用和哪些物件不在使用。
  2. 正常清除:垃圾收集器清會除不在使用的物件,回收它們的空間分配給其他物件。
  3. 壓縮清除:為了提升效能,壓縮清除會在刪除沒用的物件後,把所有存活的物件移到一起。這樣可以提高分配新物件的效率。

簡單標記和清除方法存在兩個問題:

  1. 效率很低。因為大多數新建物件都會成為“沒用物件”。
  2. 經過多次垃圾回收週期的物件很有可能在以後的週期也會存活下來。

上面簡單清除方法的問題在於Java垃圾收集的分代回收的,而且在堆記憶體裡有年輕代和年老代兩個區域。我已經在上面解釋了Minor GC和Major GC是怎樣掃描物件,以及如何把物件從一個分代空間移到另外一個分代空間。

Java垃圾回收型別

這裡有五種可以在應用裡使用的垃圾回收型別。僅需要使用JVM開關就可以在我們的應用裡啟用垃圾回收策略。讓我們一起來逐一瞭解:

  1. Serial GC(-XX:+UseSerialGC):Serial GC使用簡單的標記、清除、壓縮方法對年輕代和年老代進行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客戶端模式)很有用,比如在簡單的獨立應用和CPU配置較低的機器。這個模式對佔有記憶體較少的應用很管用。
  2. Parallel GC(-XX:+UseParallelGC):除了會產生N個執行緒來進行年輕代的垃圾收集外,Parallel GC和Serial GC幾乎一樣。這裡的N是系統CPU的核數。我們可以使用 -XX:ParallelGCThreads=n 這個JVM選項來控制執行緒數量。並行垃圾收集器也叫throughput收集器。因為它使用了多CPU加快垃圾回收效能。Parallel GC在進行年老代垃圾收集時使用單執行緒。
  3. Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一樣。不同之處,Parallel Old GC在年輕代垃圾收集和年老代垃圾回收時都使用多執行緒收集。
  4. 併發標記清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被稱為短暫停頓併發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多執行緒併發進行垃圾回收,儘量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的演算法和Parallel收集器一樣。這個垃圾收集器適用於不能忍受長時間停頓要求快速響應的應用。可使用 -XX:ParallelCMSThreads=n JVM選項來限制CMS收集器的執行緒數量。
  5. G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7後才可以使用的特性,它的長遠目標時代替CMS收集器。G1收集器是一個並行的、併發的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器執行方式不一樣,不區分年輕代和年老代空間。它把堆空間劃分為多個大小相等的區域。當進行垃圾收集時,它會優先收集存活物件較少的區域,因此叫“Garbage First”。你可以在Oracle Garbage-FIrst收集器文件找到更多詳細資訊。

Java垃圾收集監控

我們可以使用命令列和圖形工具來監控監控應用垃圾回收。例如,我使用Java SE下載頁中的一個demo來實驗。

如果你想使用同樣的應用,可以到Java SE下載頁面下載JDK 7和JavaFX演示和示例。我使用的示例應用是Java2Demo.jar,它位於 jdk1.7.0_55/demo/jfc/Java2D 目錄下。這只是一個可選步驟,你可以執行GC監控命令監控任何Java應用。

我開啟演示應用使用的命令是:

1

[email protected]:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar

jsat

可以使用jstat命令列工具監控JVM記憶體和垃圾回收。標準的JDK已經附帶了jstat,所以不需要做任何額外的事情就可以得到它。

要執行jstat你需要知道應用的程序id,你可以使用 ps -eaf | grep java 命令獲取程序id。

1

2

3

[email protected]:~$ ps -eaf | grep Java2Demo.jar

501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar

501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar

從上面知道,我的Java應用程序id是9582。現在可以執行jstat命令了,就像下面展示的一樣:

1

2

3

4

5

6

7

8

9

[email protected]:~$ jstat -gc 9582 1000

S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT

1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654

1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654

1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654

1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654

1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654

1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656

1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656

jstat命令的最後一個引數是每個輸出的時間間隔。每隔一秒就會打印出記憶體和垃圾收集資料。

讓我們一起來對每一列的意義進行逐一瞭解:

  • S0C和S1C:這一列展示了Survivor0和Survivor1區的當前大小(單位KB)。
  • S0U和S1U:這一列展示了當前Survivor0和Survivor1區的使用情況(單位KB)。注意:無論任何時候,總會有一個Survivor區是空著的。
  • EC和EU:這些列展示了Eden區當前空間大小和使用情況(單位KB)。注意:EU的大小一直在增大。而且只要大小接近EC時,就會觸發Minor GC並且EU將會減小。
  • OC和OU:這些列展示了年老代當前空間大小和當前使用情況(單位KB)。
  • PC和PU:這些列展示了Perm Gen(永久代)當前空間大小和當前使用情況(單位KB)。
  • YGC和YGCT:YGC這列顯示了發生在年輕代的GC事件的數量。YGCT這列顯示了在年輕代進行GC操作的累計時間。注意:在EU的值由於minor GC導致下降時,同一行的YGC和YGCT都會增加。
  • FGC和FGCT:FGC列顯示了發生Full GC事件的次數。FGCT顯示了進行Full GC操作的累計時間。注意:相對於年輕代的GC使用時間,Full GC所用的時間長很多。
  • GCT:這一列顯示了GC操作的總累計時間。注意:總累計時間是YGCT和FGCT兩列所用時間的總和(GCT=YGCT+FGCT)。

jstat的優點,我們同樣可以在沒有GUI的遠端伺服器上執行jstat。注意:我們是通過 -Xmn10m 選項來指定S0C、S1C和EC的總和為10m的。

Java VisualVM及Visual GC外掛

如果你想在GUI裡檢視記憶體和GC,那麼可以使用jvisualvm工具。Java VisualVM同樣是JDK的一部分,所以你不需要單獨去下載。

在終端執行jvisualvm命令啟動Java VisualVM程式。一旦啟動程式,你需要從Tools->Plugins選項安裝Visual GC外掛,就像下面圖片展示的。

安裝完Visual GC外掛後,從左邊欄開啟應用並把視角轉到Visual GC部分。你將會得到關於JVM記憶體和垃圾收集詳情,如下圖所示。

Java垃圾回收調優

Java垃圾回收調優應該是提升應用吞吐量的最後一個選擇。在你發現應用由於長時間垃圾回收導致了應用效能下降、出現超時的時候,應該考慮Java垃圾收集調優。

如果你在日誌裡看到 java.lang.OutOfMemoryError: PermGen space錯誤,那麼可以嘗試使用 -XX:PermGen 和 -XX:MaxPermGen JVM選項去監控並增加Perm Gen記憶體空間。你也可以嘗試使用-XX:+CMSClassUnloadingEnabled並檢視使用CMS垃圾收集器的執行效能。

如果你看到了大量的Full GC操作,那麼你應該嘗試增大老年代的記憶體空間。

全面垃圾收集調優要花費大量的努力和時間,這裡沒有一塵不變的硬性調優規則。你需要去嘗試不同的選項並且對這些選項進行對比,從而找出最適合自己應用的方案。

這就是所有的Java記憶體模型和垃圾回收內容。希望對你理解JVM記憶體和垃圾收集過程有所幫助。

原文連結: journaldev 翻譯: ImportNew.com 進林
譯文連結: http://www.importnew.com/14086.html