背景
永久代(PermGen)在哪裡?
根據,hotspot jvm結構如下(虛拟機棧和本地方法棧合一起了):
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuMDM2IjM3QzMtgjM0ETM0UzNxYjM0AzNxAjMtYjN4QDO18CX0AzNxAjMvwlN2gDN4UzLcd2bsJ2Lc12bj5ycn9Gbi52YuUTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
上圖引自網絡,但有個問題:方法區和heap堆都是線程共享的記憶體區域。
關于方法區和永久代
在HotSpot JVM中,這次讨論的永久代,就是上圖的方法區(JVM規範中稱為方法區)。《Java虛拟機規範》隻是規定了有方法區這麼個概念和它的作用,并沒有規定如何去實作它。在其他JVM上不存在永久代。
JDK8永久代的廢棄
JDK8 永久代變化如下圖:
- 新生代:Eden+From Survivor+To Survivor
- 老年代:OldGen
- 永久代(方法區的實作) : PermGen----->替換為Metaspace(本地記憶體中)
為什麼廢棄永久代(PermGen)
官方說明
參照JEP122:http://openjdk.java.net/jeps/122,原文截取:
Motivation
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
即:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因為JRockit沒有永久代,不需要配置永久代。
現實使用中易出問題
由于永久代記憶體經常不夠用或發生記憶體洩露,爆出異常java.lang.OutOfMemoryError: PermGen
深入了解元空間(Metaspace)
元空間的記憶體大小
元空間是方法區的在HotSpot jvm 中的實作,方法區主要用于存儲類的資訊、常量池、方法資料、方法代碼等。方法區邏輯上屬于堆的一部分,但是為了與堆進行區分,通常又叫“非堆”。
元空間的本質和永久代類似,都是對JVM規範中方法區的實作。不過元空間與永久代之間最大的差別在于:元空間并不在虛拟機中,而是使用本地記憶體。,理論上取決于32位/64位系統可虛拟的記憶體大小。可見也不是無限制的,需要配置參數。
常用配置參數
MetaspaceSize
初始化的Metaspace大小,控制元空間發生GC的門檻值。GC後,動态增加或降低MetaspaceSize。在預設情況下,這個值大小根據不同的平台在12M到20M浮動。使用Java -XX:+PrintFlagsInitial指令檢視本機的初始化參數
MaxMetaspaceSize
限制Metaspace增長的上限,防止因為某些情況導緻Metaspace無限的使用本地記憶體,影響到其他程式。在本機上該參數的預設值為4294967295B(大約4096MB)。
MinMetaspaceFreeRatio
當進行過Metaspace GC之後,會計算目前Metaspace的空閑空間比,如果空閑比小于這個參數(即實際非空閑占比過大,記憶體不夠用),那麼虛拟機将增長Metaspace的大小。預設值為40,也就是40%。設定該參數可以控制Metaspace的增長的速度,太小的值會導緻Metaspace增長的緩慢,Metaspace的使用逐漸趨于飽和,可能會影響之後類的加載。而太大的值會導緻Metaspace增長的過快,浪費記憶體。
MaxMetasaceFreeRatio
當進行過Metaspace GC之後, 會計算目前Metaspace的空閑空間比,如果空閑比大于這個參數,那麼虛拟機會釋放Metaspace的部分空間。預設值為70,也就是70%。
MaxMetaspaceExpansion
Metaspace增長時的最大幅度。在本機上該參數的預設值為5452592B(大約為5MB)。
MinMetaspaceExpansion
Metaspace增長時的最小幅度。在本機上該參數的預設值為340784B(大約330KB為)。
測試并追蹤元空間大小
測試字元串常量
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
在eclipse中選中類→run configuration→java application→new 參數如下:
由于設定了最大記憶體20M,很快就溢出,如下圖:
可見在jdk8中:
- 字元串常量由永久代轉移到堆中。
- 持久代已不存在,PermSize MaxPermSize參數已移除。(看圖中最後兩行)
測試元空間溢出
根據定義,我們以加載類來測試元空間溢出,代碼如下:
package jdk8;
import java.io.File;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
/**
*
* @ClassName:OOMTest
* @Description:模拟類加載溢出(元空間oom)
* @author diandian.zhang
* @date 2017年4月27日上午9:45:40
*/
public class OOMTest {
public static void main(String[] args) {
try {
//準備url
URL url = new File("D:/58workplace/11study/src/main/java/jdk8").toURI().toURL();
URL[] urls = {url};
//擷取有關類型加載的JMX接口
ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
//用于緩存類加載器
List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
while (true) {
//加載類型并緩存類加載器執行個體
ClassLoader classLoader = new URLClassLoader(urls);
classLoaders.add(classLoader);
classLoader.loadClass("ClassA");
//顯示數量資訊(共加載過的類型數目,目前還有效的類型數目,已經被解除安裝的類型數目)
System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
System.out.println("active: " + loadingBean.getLoadedClassCount());
System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
為了快速溢出,設定參數:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m,運作結果如下:
上圖證明了,我們的JDK8中類加載(方法區的功能)已經不在永久代PerGem中了,而是Metaspace中。可以配合JVisualVM來看,更直覺一些。
總結
本文講解了元空間(Metaspace)的由來和本質,常用配置,以及監控測試。元空間的大小是動态變更的,但不是無限大的,最好也時常關注一下大小,以免影響伺服器記憶體。
轉載:https://www.cnblogs.com/yulei126/p/6777323.html