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 | 直接內存大小,默認爲最大堆空間 |
http://lovestblog.cn/blog/2016/10/29/metaspace/