JVM 中最重要的一部分就是堆空間了,基本上大多數的線上 JVM 問題都是因為堆空間造成的 OutOfMemoryError。因此掌握 JVM 關於堆空間的參數配置對於排查線上問題非常重要。
tips:本文所有配置,如無特別說明,均基於JDK1.8。
堆配置
我們使用 -Xms 設置堆的初始空間大小,使用 -Xmx 設置堆的最大空間大小。
java -Xms20m -Xmx30m GCDemo
在上面的命令中,我們設置 JVM 的初始堆大小為 20M,最大堆空間為 30M。
年輕代
在 JDK1.8 中,堆分為年輕代和老年代。JVM 提供了參數 -Xmn 來設置年輕代內存的大小,但沒有提供參數設置老年代的大小。但其實老年代的大小就等於堆大小減去年輕代大小。
java -Xms20m -Xmn10M GCDemo
上面的命令中,我們設置 JVM 堆初始大小為20M。其中年輕代的大小為 10M,那么剩下的就是老年代的大小,有 10M了。 我們可以給上述命令加上-XX:+PrintGCDetails
參數來查看內存區域的分配信息。
如上圖所示,我們可以看到老年代的大小為 10M。
Eden區
在年輕代中,分為三個區域,分別是:eden 空間、from 空間、to 空間。如果要設置這部分的大小,那么就使用 -XX:SurvivorRatio 這個參數,該參數設置 eden / from 空間的比例關系,該參數的公式如下:
-XX:SurvivorRatio = eden/from = eden/to
例如我們的年輕代有 10 M,而我們設置 -XX:SurvivorRatio 參數為 2。也就是說 eden / from = eden / to = 2
。這里教一個快速計算的方法,我們假設 eden = 2,那么 from = 1,to = 1,那么 eden + from + to = 10M。這樣就可以算出每一份大小是 10/4 = 2.5M。所以 Eden 區 = 2.5 * 2 = 5M,from 區是 2.5 M,to 區是 2.5 M。
下面我們運行下命令來驗證一下。
java -Xms20m -Xmn10M -XX:SurvivorRatio=2 -XX:+PrintGCDetails GCDemo
在上面的啟動參數中,我們設置堆初始大小為 20M,年輕代大小為 10M,年輕代的 SurvivorRatio 比例為 2。那么最終分配的結果將會是:年輕代 10M,其中 Eden 區 5M、From 區 2.5M、To 區 2.5 M,老年代 10M。
從上圖可以看到:eden 空間是 5120 K,from 和 to 空間是 2560 K。
上圖還有一個細節,即 PSYoungGen 這里的 total 只有 7680K,難道年輕代只有 7.5M 的內存嗎?為什么不是 10M 呢?其實是因為這里的 total 指的是可用內存,from space 和 to space 兩個區域,同一時間只有一個區域是可以用的。所以可用內存是 5120 + 2560 = 7680。
永久代(JDK1.7)
在 JDK 1.8 之前,所加載的類信息都放在永久代中。我們用 -XX:PermSize 設置永久代初始大小,用 -XX:MaxPermSize 設置永久代最大大小。
java -XX:PermSize10m -XX:MaxPermSize50m -XX:+PrintGCDetails GCDemo
在上面的啟動參數中,我們設置永久代初始大小為 10M,最大大小為 50M。我們在 JDK1.7 的環境下運行上面的命令,會看到如下的 GC 日志。
在上圖中,我們可以看到永久代的大小為我們設置的 10M。
元空間(JDK1.8)
在 JDK 1.8 之前,所有加載的類信息都放在永久代中。但在 JDK1.8 之時,永久代被移除,取而代之的是元空間(Metaspace)。在元空間這塊內存中,有兩個參數很相似,它們是: -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize。
java -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=50m -XX:+PrintGCDetails GCDemo
上面的命令中,我們設置 MetaspaceSize 為 10M,MaxMetaspaceSize 為 50M。但其實它們並不是設置初始大小和最大大小的。
從上面的執行結果可以看到,Metaspace 空間的大小為 2.6M 左右,並不是我們設置的 10M。那是因為 MetaspaceSize 設置的是元空間發生 GC 的初始閾值。當達到這個值時,元空間發生 GC 操作,這個值默認是 20.8M。而 MaxMetaspaceSize 則是設置元空間的最大大小,默認基本是機器的物理內存大小。雖然可以不設置,但還是建議設置一下,因為如果一直不斷膨脹,那么 JVM 進程可能會被 OS kill 掉。
棧空間
棧空間是每個線程各自有的一塊區域,如果棧空間太小,也會導致 StackOverFlow 異常。而要設置棧空間大小,只需要使用 -Xss 參數就可以。
java -Xss2m GCDemo
上面的啟動命令設置最大棧空間為 2M。
直接內存
在 JVM 中還有一塊內存,它獨立於 JVM 的堆內存,它就是:直接內存。我們可以使用 -XX:MaxDirectMemorySize 設置最大直接內存。如果不設置,默認為最大堆空間,即 -Xmx。
java -XX:MaxDirectMemorySize=50m GCDemo
上面的啟動命令設置直接內存最大值為 50M。
當直接內存使用達到設置值時,就會觸發垃圾回收。如果不能有效釋放足夠空間,就會引發直接內存溢出導致系統的 OOM。
總結
參數 | 含義 |
---|---|
-Xms | 初始堆大小 |
-Xmx | 最大堆空間 |
-Xmn | 設置新生代大小 |
-XX:SurvivorRatio | 設置新生代eden空間和from/to空間的比例關系 |
-XX:PermSize | 方法區初始大小 |
-XX:MaxPermSize | 方法區最大大小 |
-XX:MetaspaceSize | 元空間GC閾值(JDK1.8) |
-XX:MaxMetaspaceSize | 最大元空間大小(JDK1.8) |
-Xss | 棧大小 |
-XX:MaxDirectMemorySize | 直接內存大小,默認為最大堆空間 |
參考資料
如果只是看,其實無法真正學會知識的。為了幫助大家更好地學習,我建了一個虛擬機群,專門討論學習 Java 虛擬機方面的內容,每周針對我所發文章進行討論答疑。如果你有興趣,關注「Java技術精選」公眾號,通過右下角菜單「入群交流」加我好友,小助手會拉你入群。
JVM系列目錄
- JVM基礎系列開篇:為什么要學虛擬機?
- JVM基礎系列第1講:Java 語言的前世今生
- JVM基礎系列第2講:Java 虛擬機的歷史
- JVM基礎系列第3講:到底什么是虛擬機?
- JVM基礎系列第4講:從源代碼到機器碼,發生了什么?
- JVM基礎系列第5講:字節碼文件結構
- JVM基礎系列第6講:Java虛擬機內存結構
- JVM基礎系列第7講:JVM類加載機制
- JVM基礎系列第8講:JVM 垃圾回收機制
- JVM基礎系列第9講:JVM垃圾回收器
- JVM基礎系列第10講:垃圾回收的幾種類型
- JVM基礎系列第11講:JVM參數之堆棧空間配置
- JVM基礎系列第12講:JVM參數之查看JVM參數
- JVM基礎系列第13講:JVM參數之追蹤類信息
- JVM基礎系列第14講:JVM參數之GC日志配置
- JVM基礎系列第15講:JDK性能監控命令