天天看點

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

一、什麼是JVM

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

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

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

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

    從上圖能清晰看到Java平台包含的各個邏輯子產品,也能了解到JDK與JRE的差別,對于JVM自身的實體結構,我們可以從下圖鳥瞰一下:

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

Java代碼編譯是由Java源碼編譯器來完成,流程圖如下所示:

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

  • Java源碼編譯機制
  • 類加載機制
  • 類執行機制

Java源碼編譯機制

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

  • 分析和輸入到符号表
  • 注解處理
  • 語義分析和生成class檔案

流程圖如下所示:

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

  • 結構資訊。包括class檔案格式版本号及各部分的數量與大小的資訊
  • 中繼資料。對應于Java源碼中聲明與常量的資訊。包含類/繼承的超類/實作的接口的聲明資訊、域與方法聲明資訊和常量池
  • 方法資訊。對應Java源碼中語句和表達式對應的資訊。包含位元組碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符号資訊

類加載機制

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

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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),程式計數器存放下一條要執行的指令在方法内的偏移量,棧中存放一個個棧幀,每個棧幀對應着每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用于存放方法中的局部變量和參數,操作數棧中用于存放方法執行過程中産生的中間結果。棧的結構如下圖所示:

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

JVM記憶體組成結構

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

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

1)堆

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

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:
  • 新生代。建立的對象都是用新生代配置設定記憶體,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例
  • 舊生代。用于存放新生代中經過多次垃圾回收仍然存活的對象
  • 持久帶(Permanent Space)實作方法區,主要存放所有已加載的類資訊,方法資訊,常量池等等。可通過-XX:PermSize和-XX:MaxPermSize來指定持久帶初始化值和最大值。Permanent Space并不等同于方法區,隻不過是Hotspot JVM用Permanent Space來實作方法區而已,有些虛拟機沒有Permanent Space而用其他機制來實作方法區。
Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:
  • -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):

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

    此算法執行分兩階段。第一階段從引用根節點開始标記所有被引用的對象,第二階段周遊整個堆,把未标記的對象清除。此算法需要暫停整個應用,同時,會産生記憶體碎片。

複制(Copying):

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

标記-整理(Mark-Compact):

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

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

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)記憶體模型

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插件,就像下面圖檔展示的。

Java性能系一(JVM、垃圾回收、記憶體調優、常見參數)調優續:

安裝完Visual GC插件後,從左邊欄打開應用并把視角轉到Visual GC部分。你将會得到關于JVM記憶體和垃圾收集詳情,如下圖所示。

Java性能系一(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