深入理解JVM(三)——配置引數

飄揚的紅領巾發表於2017-08-15

JVM配置引數分為三類引數:

1、跟蹤引數

2、堆分配引數

3、棧分配引數

這三類引數分別用於跟蹤監控JVM狀態,分配堆記憶體以及分配棧記憶體。

跟蹤引數

跟蹤引數用於跟蹤監控JVM,往往被開發人員用於JVM調優以及故障排查。

1、當發生GC時,列印GC簡要資訊

使用-XX:+PrintGC或-verbose:gc引數

這兩個配置引數效果是一樣的,都是在發生GC時列印出簡要的資訊,例如執行程式碼:

1: public static void main(String[] args) 2: { 3: byte[] bytes =null; 4: for(int i=0;i<100;i++){ 5: bytes = new byte[1 * 1024 * 1024]; 6: } 7: }

這個程式連續建立了100個1M的陣列物件,使用-XX:+PrintGC或-verbose:gc引數執行該程式,即可檢視到GC情況:

1: [GC (Allocation Failure) 32686K->1648K(123904K), 0.0007230 secs] 2: [GC (Allocation Failure) 34034K->1600K(123904K), 0.0009652 secs] 3: [GC (Allocation Failure) 33980K->1632K(123904K), 0.0005306 secs]

我們可以看到程式執行了3次GC(minor GC),這三次GC都是新生代的GC,因為這個程式每次建立新的陣列物件,都會把新的物件賦給bytes變數,而老的物件沒有任意物件引用它,老對物件會變的不可達,這些不可達的物件在新生代minor GC時候被回收掉。

32686K表示回收前,物件佔用空間。1648K表示回收後,物件佔用空間。123904K表示還有多少空間可用。0.0007230 secs表示這次垃圾回收花的時間。

2、列印GC的詳細資訊以及堆使用詳細資訊

使用-XX:+PrintGCDetails引數

1: [GC (Allocation Failure) [PSYoungGen: 32686K->1656K(37888K)] 32686K->1664K(123904K), 0.0342788 secs] [Times: user=0.00 sys=0.00, real=0.03 secs] 2: [GC (Allocation Failure) [PSYoungGen: 34042K->1624K(70656K)] 34050K->1632K(156672K), 0.0013466 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 3: Heap 4: PSYoungGen total 70656K, used 43118K [0x00000000d6100000, 0x00000000dab00000, 0x0000000100000000) 5: eden space 65536K, 63% used [0x00000000d6100000,0x00000000d8985ac8,0x00000000da100000) 6: from space 5120K, 31% used [0x00000000da600000,0x00000000da796020,0x00000000dab00000) 7: to space 5120K, 0% used [0x00000000da100000,0x00000000da100000,0x00000000da600000) 8: ParOldGen total 86016K, used 8K [0x0000000082200000, 0x0000000087600000, 0x00000000d6100000) 9: object space 86016K, 0% used [0x0000000082200000,0x0000000082202000,0x0000000087600000) 10: Metaspace used 2669K, capacity 4486K, committed 4864K, reserved 1056768K 11: class space used 288K, capacity 386K, committed 512K, reserved 1048576K

我們看到除了列印GC資訊之外,還顯示了堆使用情況,堆分為新生代、老年代、元空間。注意這裡沒有永久區了,永久區在java8已經移除,原來放在永久區的常量、字串靜態變數都移到了元空間,並使用本地記憶體。

新生代當中又分為伊甸區(eden)和倖存區(from和to),從上面列印的內容可以看到新生代總大小為70656K,使用了43118K,細心的同學的可能會發現eden+from+to=65536K+5120K+5120K=75776 並不等於總大小70656K,這是為什麼呢?這是因為新生代的垃圾回收演算法是採用複製演算法,簡單的說就是在from和to之間來回複製(複製過程中再把不可達的物件回收掉),所以必須保證其中一個區是空的,這樣才能有預留空間存放複製過來的資料,所以新生代的總大小其實等於eden+from(或to)=65536K+5120K=70656k。

3、使用外部檔案記錄GC的日誌

還有一個非常有用的引數,它可以把GC的日誌記錄到外部檔案中,這在生產環境進行故障排查時尤為重要,當java程式出現OOM時,總希望看到當時垃圾回收的情況,通過這個引數就可以把GC的日誌記錄下來,便於排查問題,當然也可以做日常JVM監控。

-Xloggc:log/gc.log

R)A5CI(P(4X50Y}}NVJSTTU

4、監控類的載入

-XX:+TraceClassLoading

使用這個引數可以監控java程式載入的類:

PF}FPBLI4UP~GMKVNKGS10L

堆配置引數

指定最大堆,最小堆:Xmx、Xms

這兩個引數是我們最熟悉最常用的引數,可以用以下程式碼列印出目前記憶體使用的情況:

1: public static void main(String[] args) 2: { 3: System.out.println("最大堆:"+Runtime.getRuntime().maxMemory()/1024/1024+"M"); 4: System.out.println("空閒堆:"+Runtime.getRuntime().freeMemory()/1024/1024+"M"); 5: System.out.println("總的堆:"+Runtime.getRuntime().totalMemory()/1024/1024+"M"); 6: }

最大堆也就是Xmx引數指定的大小,表示java程式最大能使用多少記憶體大小,如果超過這個大小,那麼java程式會報:out of memory

 

(OOM錯誤),空閒堆表示程式已經分配的記憶體大小減去已經使用的記憶體大小,而總的堆表示目前程式已經配置到多少記憶體大小,一般而言程式一啟動,會按照-Xms5m先分配5M的空間,這時總的堆大小就是5M。

指定新生代記憶體大小:Xmn,例如我們指定-Xmx20m -Xms5m -Xmn2m -XX:+PrintGCDetails

1: 最大堆:19.5M 2: 空閒堆:4.720428466796875M 3: 總的堆:5.5M 4: Heap 5: PSYoungGen total 1536K, used 819K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000) 6: eden space 1024K, 79% used [0x00000000ffe00000,0x00000000ffeccc80,0x00000000fff00000) 7: from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) 8: to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) 9: ParOldGen total 4096K, used 0K [0x00000000fec00000, 0x00000000ff000000, 0x00000000ffe00000) 10: object space 4096K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff000000) 11: Metaspace used 2723K, capacity 4486K, committed 4864K, reserved 1056768K 12: class space used 293K, capacity 386K, committed 512K, reserved 1048576K

可以看到新生代總大小為eden+from+to=1024k+512k+512k=2M,和我們設定的-Xmn相對應。

 

新生代(eden+from+to)和老年代(不包含永久區)的比值:-XX:NewRatio

例如我們設定引數:-Xmx20m -Xms20m -XX:NewRatio=4 -XX:+PrintGCDetails(注意這裡改引數為4表示新生代和老年代比值為1:4)

1: 最大堆:19.5M 2: 空閒堆:8.665084838867188M 3: 總的堆:19.5M 4: Heap 5: PSYoungGen total 3584K, used 916K [0x00000000ffc00000, 0x0000000100000000, 0x0000000100000000) 6: eden space 3072K, 29% used [0x00000000ffc00000,0x00000000ffce52f8,0x00000000fff00000) 7: from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) 8: to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) 9: ParOldGen total 16384K, used 10240K [0x00000000fec00000, 0x00000000ffc00000, 0x00000000ffc00000) 10: object space 16384K, 62% used [0x00000000fec00000,0x00000000ff600010,0x00000000ffc00000) 11: Metaspace used 2723K, capacity 4486K, committed 4864K, reserved 1056768K 12: class space used 293K, capacity 386K, committed 512K, reserved 1048576K

 

可以看到新生代:eden+from+to=3072+512+512=4096k,老年代:16384k,新生代:老年代=4096k:16384k=1:4 和-XX:NewRatio=4吻合。

Eden區與Survivor區(from、to)的大小比值:-XX:SurvivorRatio(如設定為8,則兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區佔整個年輕代的1/10)

例如設定引數-Xmx20m -Xms20m -Xmn8m -XX:SurvivorRatio=6 -XX:+PrintGCDetails

這個引數設定了新生代記憶體大小為8m,並設定Survivor區與一個Eden區的比值為2:6,來看看列印資訊:

1: 最大堆:19.0M 2: 空閒堆:8.104576110839844M 3: 總的堆:19.0M 4: Heap 5: PSYoungGen total 7168K, used 1040K [0x00000000ff800000, 0x0000000100000000, 0x0000000100000000) 6: eden space 6144K, 16% used [0x00000000ff800000,0x00000000ff904090,0x00000000ffe00000) 7: from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) 8: to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) 9: ParOldGen total 12288K, used 10240K [0x00000000fec00000, 0x00000000ff800000, 0x00000000ff800000) 10: object space 12288K, 83% used [0x00000000fec00000,0x00000000ff600010,0x00000000ff800000) 11: Metaspace used 2723K, capacity 4486K, committed 4864K, reserved 1056768K 12: class space used 293K, capacity 386K, committed 512K, reserved 1048576K

Survivor區=from+to=2048,Eden區=6144K,Survivor區:Eden區=2:6,和-XX:SurvivorRatio=6吻合。

其他還有-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath這兩個引數可以實現在發生OOM異常時把堆疊資訊列印到外部檔案。

堆分配引數的總結

根據實際事情調整新生代和倖存代的大小

官方推薦新生代佔堆的3/8

倖存代佔新生代的1/10

在OOM時,記得Dump出堆,確保可以排查現場問題

永久區分配引數

-XX:PermSize -XX:MaxPermSize

用於設定永久區的初始空間和最大空間,他們表示一個系統可以容納多少個型別,一般空間比較小。在java1.8以後,永久區被移到了後設資料區,使用本地記憶體,所以這兩個引數也不建議再使用。

棧大小分配引數

棧大小引數為-Xss,通常只有幾百k,決定了函式呼叫的深度,每個執行緒都有自己獨立的棧空間。如果函式呼叫太深,超過了棧的大小,則會丟擲java.lang.StackOverflowError,通常我們遇到這種錯誤,不是去調整-Xss引數,而是應該去調查函式呼叫太深的原理,是否使用遞迴,能不能保證遞迴出口等。

小結

本文講解了JVM常用的引數,涉及跟蹤、堆、永久區、棧的分配,其中最重要最常用的是跟蹤、堆的分配引數,他們也和調優、故障排查息息相關。

相關文章