7.JVM調優-方法區,堆,棧調優詳解
通常我們都知道在堆空間新生代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次的時候, 發生了棧溢位。