1.背景
2.為什么廢棄永久代(PermGen)
3.深入理解元空間(Metaspace)
4.總結
========正文分割線=====
一、背景
1.1 永久代(PermGen)在哪里?
根據,hotspot jvm結構如下(虛擬機棧和本地方法棧合一起了):

上圖引自網絡,但有個問題:方法區和heap堆都是線程共享的內存區域。
關於方法區和永久代:
在HotSpot JVM中,這次討論的永久代,就是上圖的方法區(JVM規范中稱為方法區)。《Java虛擬機規范》只是規定了有方法區這么個概念和它的作用,並沒有規定如何去實現它。在其他JVM上不存在永久代。
1.2 JDK8永久代的廢棄
JDK8 永久代變化如下圖:

1.新生代:Eden+From Survivor+To Survivor
2.老年代:OldGen
3.永久代(方法區的實現) : PermGen----->替換為Metaspace(本地內存中)
二、為什么廢棄永久代(PermGen)
2.1 官方說明
參照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沒有永久代,不需要配置永久代。
2.2 現實使用中易出問題
由於永久代內存經常不夠用或發生內存泄露,爆出異常java.lang.OutOfMemoryError: PermGen
三、深入理解元空間(Metaspace)
3.1元空間的內存大小
元空間是方法區的在HotSpot jvm 中的實現,方法區主要用於存儲類的信息、常量池、方法數據、方法代碼等。方法區邏輯上屬於堆的一部分,但是為了與堆進行區分,通常又叫“非堆”。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。,理論上取決於32位/64位系統可虛擬的內存大小。可見也不是無限制的,需要配置參數。
3.2常用配置參數
1.MetaspaceSize
初始化的Metaspace大小,控制元空間發生GC的閾值。GC后,動態增加或降低MetaspaceSize。在默認情況下,這個值大小根據不同的平台在12M到20M浮動。使用Java -XX:+PrintFlagsInitial命令查看本機的初始化參數
2.MaxMetaspaceSize
限制Metaspace增長的上限,防止因為某些情況導致Metaspace無限的使用本地內存,影響到其他程序。在本機上該參數的默認值為4294967295B(大約4096MB)。
3.MinMetaspaceFreeRatio
當進行過Metaspace GC之后,會計算當前Metaspace的空閑空間比,如果空閑比小於這個參數(即實際非空閑占比過大,內存不夠用),那么虛擬機將增長Metaspace的大小。默認值為40,也就是40%。設置該參數可以控制Metaspace的增長的速度,太小的值會導致Metaspace增長的緩慢,Metaspace的使用逐漸趨於飽和,可能會影響之后類的加載。而太大的值會導致Metaspace增長的過快,浪費內存。
4.MaxMetasaceFreeRatio
當進行過Metaspace GC之后, 會計算當前Metaspace的空閑空間比,如果空閑比大於這個參數,那么虛擬機會釋放Metaspace的部分空間。默認值為70,也就是70%。
5.MaxMetaspaceExpansion
Metaspace增長時的最大幅度。在本機上該參數的默認值為5452592B(大約為5MB)。
6.MinMetaspaceExpansion
Metaspace增長時的最小幅度。在本機上該參數的默認值為340784B(大約330KB為)。
3.3測試並追蹤元空間大小
3.3.1.測試字符串常量
1 public class StringOomMock {
2 static String base = "string";
3
4 public static void main(String[] args) {
5 List<String> list = new ArrayList<String>();
6 for (int i=0;i< Integer.MAX_VALUE;i++){
7 String str = base + base;
8 base = str;
9 list.add(str.intern());
10 }
11 }
12 }
在eclipse中選中類--》run configuration-->java application--》new 參數如下:

由於設定了最大內存20M,很快就溢出,如下圖:

可見在jdk8中:
1.字符串常量由永久代轉移到堆中。
2.持久代已不存在,PermSize MaxPermSize參數已移除。(看圖中最后兩行)
3.3.2.測試元空間溢出
根據定義,我們以加載類來測試元空間溢出,代碼如下:
1 package jdk8;
2
3 import java.io.File;
4 import java.lang.management.ClassLoadingMXBean;
5 import java.lang.management.ManagementFactory;
6 import java.net.URL;
7 import java.net.URLClassLoader;
8 import java.util.ArrayList;
9 import java.util.List;
10
11 /**
12 *
13 * @ClassName:OOMTest
14 * @Description:模擬類加載溢出(元空間oom)
15 * @author diandian.zhang
16 * @date 2017年4月27日上午9:45:40
17 */
18 public class OOMTest {
19 public static void main(String[] args) {
20 try {
21 //准備url
22 URL url = new File("D:/58workplace/11study/src/main/java/jdk8").toURI().toURL();
23 URL[] urls = {url};
24 //獲取有關類型加載的JMX接口
25 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
26 //用於緩存類加載器
27 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
28 while (true) {
29 //加載類型並緩存類加載器實例
30 ClassLoader classLoader = new URLClassLoader(urls);
31 classLoaders.add(classLoader);
32 classLoader.loadClass("ClassA");
33 //顯示數量信息(共加載過的類型數目,當前還有效的類型數目,已經被卸載的類型數目)
34 System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
35 System.out.println("active: " + loadingBean.getLoadedClassCount());
36 System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
37 }
38 } catch (Exception e) {
39 e.printStackTrace();
40 }
41 }
42 }
為了快速溢出,設置參數:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m,運行結果如下:

上圖證實了,我們的JDK8中類加載(方法區的功能)已經不在永久代PerGem中了,而是Metaspace中。可以配合JVisualVM來看,更直觀一些。
四、總結
本文講解了元空間(Metaspace)的由來和本質,常用配置,以及監控測試。元空間的大小是動態變更的,但不是無限大的,最好也時常關注一下大小,以免影響服務器內存。

