JVM 參數(shù):配置堆空間與棧空間
1. 前言
本節(jié)內(nèi)容主要是學(xué)習(xí) JVM 配置堆空間與??臻g的常用參數(shù)配置,堆空間和??臻g這兩塊內(nèi)存區(qū)域是非常重要的運(yùn)行時(shí)數(shù)據(jù)存放區(qū),掌握堆空間與棧空間的參數(shù)配置,在實(shí)際工作中非常重要。本節(jié)主要知識(shí)點(diǎn)如下:
- 理解并掌握配置堆空間的參數(shù) -Xms 和 -Xmx,并配和跟蹤垃圾回收參數(shù) -XX:+PrintGCDetails 驗(yàn)證堆空間是否配置成功,為本節(jié)重點(diǎn)內(nèi)容;
- 理解并掌握配置年輕代的參數(shù) -Xmn,并配和跟蹤垃圾回收參數(shù) -XX:+PrintGCDetails 驗(yàn)證堆空間是否配置成功,為本節(jié)重點(diǎn)內(nèi)容;
- 理解并掌握配置元空間的參數(shù) -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize,并配和跟蹤垃圾回收參數(shù) -XX:+PrintGCDetails 驗(yàn)證堆空間是否配置成功,為本節(jié)重點(diǎn)內(nèi)容;
- 理解并掌握配置棧空間的參數(shù) -Xss,為本節(jié)次重點(diǎn)內(nèi)容;
JVM 配置堆空間與??臻g的常用參數(shù),是非常重要的知識(shí)點(diǎn),需要在理解的基礎(chǔ)上,并掌握參數(shù)的使用方法。堆空間更加詳細(xì)的內(nèi)部結(jié)構(gòu)會(huì)在后續(xù)的課程中做專門講解,此處先掌握配置方法即可。
2. 示例代碼準(zhǔn)備
本節(jié)主要是為了學(xué)習(xí)配置堆空間和??臻g的參數(shù),因此不需要像跟蹤垃圾回收那樣手動(dòng)調(diào)用 gc 方法,也不需要像跟蹤類的加載與卸載那樣建立一個(gè) ArrayList 來觀察 ArrayList 類的加載。所以此處的實(shí)例代碼非常簡(jiǎn)單,隨意打印一行字符串即可,我們主要的關(guān)注點(diǎn)在配置完堆空間和??臻g之后是否生效。
示例:
public class HeapAndStackParamsDemo {
public static void main(String[] args) {
System.out.println("Heap and Stack!");
}
}
3. -Xms 和 -Xmx 參數(shù)
參數(shù)作用:
- -Xms:設(shè)置堆的初始空間大?。?/li>
- -Xmx:設(shè)置堆的最大空間大小。
Tips:多數(shù)情況下,這兩個(gè)參數(shù)是配合使用的,設(shè)置完初始空間大小后,為了對(duì)堆空間的最大值有一個(gè)把控,還需要設(shè)置下堆空間的最大值。
場(chǎng)景設(shè)置:設(shè)置堆的初始空間大小為 10 M,設(shè)置堆的最大空間大小為 20 M。(此處設(shè)置的空間大小為實(shí)驗(yàn)數(shù)據(jù),具體值的設(shè)置,需要根據(jù)不同項(xiàng)目的實(shí)際情況而定。)
- 步驟 1:在 VM Options 中配置參數(shù) -Xms10m -Xmx20m -XX:+PrintGCDetails 并保存;
Tips:為了驗(yàn)證參數(shù)設(shè)置是否成功,我們需要配合使用 - XX:+PrintGCDetails 來獲取堆空間的空間大小,因此此處的參數(shù)需要添加上 - XX:+PrintGCDetails,此處僅為驗(yàn)證,正常情況下,堆空間的設(shè)置是單獨(dú)使用的,如: -Xms10m -Xmx20m。
- 步驟 2:運(yùn)行示例代碼,觀察執(zhí)行結(jié)果。
結(jié)果驗(yàn)證:
Heap
PSYoungGen total 2560K, used 2012K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 98% used [0x00000000ffd00000,0x00000000ffef7388,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 367K, capacity 388K, committed 512K, reserved 1048576K
結(jié)果分析:我們實(shí)驗(yàn)場(chǎng)景的設(shè)計(jì)中,設(shè)置了堆空間的大小初始化為 10 M,那么換算成 Kb 為 10 M = 10240 Kb。
Tips:我們從打印的結(jié)果中看到了三部分內(nèi)存,PSYoungGen (年輕代),ParOldGen(老年代),Metaspace (元空間)。從 JDK1.8 開始,Metaspace (元空間)不屬于堆空間,目前我使用的 JDK 大版本號(hào)為 1.8 ,因此對(duì)于堆空間的初始化大小 10 M,應(yīng)該只分配給了 PSYoungGen (年輕代)和 ParOldGen(老年代)。
提出問題:我們來進(jìn)行下計(jì)算,(PSYoungGen total)2560K + (ParOldGen total)7168K = 9728 K。為什么不等于 10240 K? 是什么原因呢?
問題解決:其實(shí)是因?yàn)檫@里的 total 指的是可用內(nèi)存,我們來看下 PSYoungGen (年輕代)的全部日志:
PSYoungGen total 2560K, used 2012K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 98% used [0x00000000ffd00000,0x00000000ffef7388,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
我們可以看到,PSYoungGen(年輕代)包含了 eden 區(qū)以及 from space 和 to space 兩個(gè)區(qū)域,同一時(shí)間,from space 和 to space 只有一個(gè)區(qū)域是可以用的。所以分配給 PSYoungGen(年輕代)的總內(nèi)存是 2560 K+ 512 K= 3072 K。
我們?cè)購男伦鱿掠?jì)算,(PSYoungGen total)3072 K+ (ParOldGen total)7168K = 10240 K。到此,證明了參數(shù)設(shè)置的有效性。
4. -Xmn 參數(shù)
參數(shù)作用:專門的設(shè)置年輕代 PSYoungGen 大小的參數(shù)。
場(chǎng)景設(shè)置:為了更好的理解并掌握 -Xmn 參數(shù),我們沿用上一知識(shí)點(diǎn)的 -Xms10m -Xmx20m -XX:+PrintGCDetails 參數(shù),在此參數(shù)的基礎(chǔ)上,添加 -Xmn5m ,單獨(dú)設(shè)置年輕代 PSYoungGen 的大小為 5m。
Tips:前文講解過,堆空間大小 = 年輕代空間大小 + 老年代空間大小,此處設(shè)置堆空間初始大小為 10m,年輕代大小為 5m, 那么通過簡(jiǎn)單的計(jì)算,老年代的空間大小為 10m - 5m = 5m。我們繼續(xù)來看實(shí)驗(yàn)步驟和結(jié)果驗(yàn)證。
- 步驟 1:在 VM Options 中配置參數(shù) -Xms10m -Xmx20m -Xmn5m -XX:+PrintGCDetails 并保存;
- 步驟 2:運(yùn)行示例代碼,觀察執(zhí)行結(jié)果。
結(jié)果驗(yàn)證:
Heap
PSYoungGen total 4608K, used 2142K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
eden space 4096K, 52% used [0x00000000ffb00000,0x00000000ffd179c0,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 5120K, used 0K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
object space 5120K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffb00000)
Metaspace used 3441K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 374K, capacity 388K, committed 512K, reserved 1048576K
結(jié)果分析:我們主要來關(guān)注下 PSYoungGen(年輕代)的大小,看是否為 5m,換算成 Kb 為 5120 Kb。前文提到過,total 僅代表可用內(nèi)存,而同一時(shí)間 from space 和 to space 只有一個(gè)是可用的,所以 PSYoungGen(年輕代)總內(nèi)存的大小為 4608K + 512K = 5120K = 5m。
5. -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 參數(shù)
Tips:在 JDK 1.8 之前,所有加載的類信息都放在永久代中。但在 JDK1.8 之時(shí),永久代被移除,取而代之的是元空間(Metaspace)。一些版本比較低的教程或者論壇,經(jīng)常會(huì)在忽略 JDK 版本的前提下談?dòng)谰么蛘咴臻g,不要被此類教程迷惑,此處同學(xué)要特別注意。
參數(shù)作用:
- -XX:MetaspaceSize :元空間發(fā)生 GC 的初始閾值;
Tips:-XX:MetaspaceSize 這個(gè)參數(shù)并非設(shè)置元空間初始大小,而是設(shè)置的發(fā)生 GC 的初始閾值。舉例來說,如果設(shè)置 -XX:MetaspaceSize 為 10m,那么當(dāng)元空間的數(shù)據(jù)存儲(chǔ)量到達(dá) 10m 時(shí),就會(huì)發(fā)生 GC。
- -XX:MaxMetaspaceSize :設(shè)置元空間的最大空間大小。
場(chǎng)景設(shè)置:設(shè)置元空間發(fā)生 GC 的初始閾值的大小為 10 M,設(shè)置元空間的最大空間大小為 20 M。(此處設(shè)置的空間大小為實(shí)驗(yàn)數(shù)據(jù),具體值的設(shè)置,需要根據(jù)不同項(xiàng)目的實(shí)際情況而定。)
我們通過兩步來進(jìn)行驗(yàn)證:
- 步驟 1:在 VM Options 中配置參數(shù) -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=20m -XX:+PrintGCDetails 并保存;
- 步驟 2:運(yùn)行示例代碼,觀察執(zhí)行結(jié)果。
結(jié)果驗(yàn)證:
Heap
PSYoungGen total 76288K, used 5244K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000)
eden space 65536K, 8% used [0x000000076b400000,0x000000076b91f0d8,0x000000076f400000)
from space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000)
to space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000)
ParOldGen total 175104K, used 0K [0x00000006c1c00000, 0x00000006cc700000, 0x000000076b400000)
object space 175104K, 0% used [0x00000006c1c00000,0x00000006c1c00000,0x00000006cc700000)
Metaspace used 3392K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 368K, capacity 388K, committed 512K, reserved 1048576K
結(jié)果分析:從上面的執(zhí)行結(jié)果可以看到,Metaspace 空間的初始大小為 3392K ,并不是我們?cè)O(shè)置的 10M。那是因?yàn)?-XX:MetaspaceSize 設(shè)置的是元空間發(fā)生 GC 的初始閾值。當(dāng)達(dá)到這個(gè)值時(shí),元空間發(fā)生 GC 操作。如果不進(jìn)行設(shè)置,這個(gè)值默認(rèn)是 20.8M。
而 -XX:MaxMetaspaceSize 則是設(shè)置元空間的最大值,如果不手動(dòng)設(shè)置,默認(rèn)基本是機(jī)器的物理內(nèi)存大小。雖然可以不設(shè)置,但還是建議設(shè)置一下,因?yàn)槿绻恢辈粩嗯蛎?,那?JVM 進(jìn)程可能會(huì)被 OS kill 掉。
6. -Xss 參數(shù)
參數(shù)作用:設(shè)置單個(gè)線程棧大小,一般默認(rèn) 512 - 1024kb。
Tips:由于單個(gè)線程棧大小跟操作系統(tǒng)和 JDK 版本都有關(guān)系,因此其默認(rèn)大小是一個(gè)范圍值, 512 - 1024kb。在平時(shí)工作中,-Xss 參數(shù)使用到的場(chǎng)景是非常少的,因?yàn)閱蝹€(gè)線程的??臻g大小使用默認(rèn)的 512 - 1024kb 就能夠滿足需求。
如果在某些個(gè)別場(chǎng)景下,單個(gè)線程的??臻g發(fā)生內(nèi)存溢出,多數(shù)情況是由于迭代的深度達(dá)到了棧的最大深度,導(dǎo)致內(nèi)存溢出。這種異常情況,多數(shù)會(huì)選擇優(yōu)化方法,并不是立刻提升??臻g大小,因?yàn)槊つ刻嵘龡?臻g大小,是一種資源浪費(fèi)。
-Xss 參數(shù)的使用為本節(jié)課程的次重點(diǎn),學(xué)習(xí)者只要了解并掌握該參數(shù)的作用即可,萬一工作中碰到設(shè)置??臻g大小的場(chǎng)景,也不至于束手無措。
7. 小結(jié)
本小節(jié)的重點(diǎn)內(nèi)容即我們所講述的幾種設(shè)置堆空間的參數(shù),學(xué)習(xí)者需要對(duì)這些常用參數(shù)的意義以及使用方式進(jìn)行掌握,在實(shí)際工作中,使用頻次非常高。對(duì)于??臻g的參數(shù)設(shè)置,為本節(jié)次重點(diǎn),因?yàn)槭褂脠?chǎng)景比較少,學(xué)習(xí)者只要有此參數(shù)的印象即可,做到用時(shí)可用。