通常我們都知道在堆空間新生代Eden區滿了,會觸發minor GC, 在老年代滿了會觸發full GC, 觸發full GC會導致Stop The World, 那你們知道還有一個區域滿了一會觸發Full GC么?而且這個區域滿了會直接影響我們的開發效率。
一、方法區參數調優
我們可以對運行時數據區的內存進行參數設置. 這是jvm調優的重點. 參數的變化將影響到整體效率

核心參數設置如下:
java -Xms2048M -Xmx1024M -Xss512k -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar
這是一個通用的設置。圖中具體含義如下:
- -Xms:堆空間最小值
- -Xmx:堆空間最大值
- -Xmn:新生代占堆空間的大小
- -XX:MetaspaceSize:方法區(元空間)初始值
- -XX:MaxMetaspaceSize:方法區(元空間)最大值
- -Xss:每一個線程的空間大小
下面主要研究方法區參數設置
1. 方法區(元空間)參數設置

在jdk8之前有個區域叫做永久代, 在jdk8及以后改名字了, 叫做元空間. 這塊內存空間占用的是直接的物理內存.
元空間有一個特點: 可以動態擴容。如果, 我們沒有設置元空間的上限, 那么他可以擴大到整個內存. 比如內存條是8G的, 堆和棧分配了4G的空間, 那么元空間最多可以使用4G。
我們可以通過參數來設置使用的元空間內存。
對於64位的JVM來說, 元空間默認大小是21M, 元空間的默認最大值是無上限的, 他的上限就是內存空間
- -XX:MetaspaceSize: 元空間的初始空間大小, 以字節位單位, 默認是21M,達到該值就會觸發full GC, 同時收集器會對該值進行調整, 如果釋放了大量的空間, 就適當降低該值, 如果釋放了很少的空間, 提升該值,但最到不超過-XX:MaxMetaspaceSize設置的值
比如:
初始值是21M, 第一次回收了20M, 那么只有1M沒有被回收, 下一次, 元空間會自動調整大小, 可能會調整到15M
初始大小依然是21M, 第二次回收發現回收了1M, 有20M沒有被回收, 他就會自動擴大空間, 可能擴大到30M,也可能是40M
- -XX:MaxMetaspaceSize: 設置元空間的最大值, 默認是-1, 即不限制, 或者說只受限於本地內存的大小
由於調整元空間的大小需要full GC, 這是非常昂貴的操作, 如果應用在啟動的時候發生大量的full GC, 通常都是由於永久代或元空間發生了大小的調整, 基於這種情況, 一般建議在JVM參數中將-XX:MetaspaceSize和-XX:MaxMetaspaceSize設置成一樣的值, 並設置的比初始值還要大, 對於8G物理內存的機器來說, 一般會將這兩個值設置為256M或者512M都可以
2. 建議設置元空間值, 如果不設置會怎么樣?
不設置元空間默認就是21M, 很容易就會放滿, 通常我們的war可能都是幾十M, 甚至幾個G. 如果我們在啟動程序的時候, 會啟動幾分鍾. 這很有可能是沒有設置元空間的大小.
放滿后會發生full GC, 然后在擴大一點元空間, 擴大到25M, 重新開始, 過了一會又放滿了, 再次full GC, 在擴大一點, 元空間擴大到30M, 就這樣一直發生full GC, 然后一直擴大元空間, 直到擴大的元空間大小合適, 不再發生full gc, 程序才會正常啟動運行. 這是個很耗時耗性能的操作, 這樣的full GC也是沒有必要的.
如果項目啟動較慢,多次重復啟動,考慮是不是元空間設置不合理,或者內存不夠導致。
二. 線程棧參數調優
-Xss512k:設置棧空間參數的
這個參數就是用來設置棧空間的. 他是設置的一個線程棧占用的空間, 一個程序啟動后可能有多個線程棧, 那么他們占用的空間都是512k。
一個程序啟動以后,系統為其分配的棧空間是固定的。理論上來說,如果每一個線程占用的空間少,那么就能分配更多的線程。否則則相反。但這也不是確定的,還有和系統的cpu,內存有關系。
當線程分配的空間用完的時候,就會拋出棧溢出異常。下面來看一個例子
package com.lxl.jvm;
public class StackOverflowTest {
/**
* jvm 設置-Xss128M, (默認是1M)
*/
static int count = 0;
public static void redo(){
count ++;
redo();
}
public static void main(String[] args) {
try {
redo();
}catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
}
這里定義了一個變量count, main方法里調用了redo()方法. 當我們執行main方法的時候, 線程棧模型是什么樣的呢?

當程序執行到main方法的時候, 會在線程棧中開辟一個main方法的棧幀
繼續執行, 執行到redo()的時候, 會在線程棧在開辟一塊redo方法棧幀
redo方法里又調用了redo方法. 繼續開辟一塊redo方法棧幀,
.......
棧幀是占用內存空間的. 總有一個時刻會把棧內存消耗完. 就會報棧內存溢出了

我們看到程序一共運行了16979次發生了棧溢出.
當棧空間設置的小一些呢?比如256k

我們運行看效果

當運行到2079次的時候, 發生了棧溢出。