Java虛擬機16:Metaspace

被廢棄的持久代

想起之前面試的時候有面試官問起過我一個問題:Java 8為什麼要廢棄持久代即Metaspace的作用。由於當時使用的Java 7且研究重心不在JVM上,一下沒有回答上來,今天突然想起這個問題,就詳細總結一下這個問題。

首先我們看一張JVM內存佈局的圖:

Java虛擬機16:Metaspace

注意到裡面有一塊METHOD AREA,它是一塊線程共享的對象,名為方法區,在HotSpot虛擬機中,這塊METHOD AREA我們可以認為等同於持久代(PermGen),在Java 6及之前的版本,持久代存放了以下一些內容:

  • 虛擬機加載的類信息
  • 常量池
  • 靜態變量
  • 即時編譯後的代碼

到了Java 7之後,常量池已經不在持久代之中進行分配了,而是移到了堆中,即常量池和對象共享堆內存。

接著到了Java 8之後的版本(至此篇文章,Java 10剛發佈),持久代已經被永久移除,取而代之的是Metaspace。

為什麼要移除持久代

HotSpot團隊選擇移除持久代,有內因和外因兩部分,從外因來說,我們看一下JEP 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.

大致就是說移除持久代也是為了和JRockit進行融合而做的努力,JRockit用戶並不需要配置持久代(因為JRockit就沒有持久代)。

從內因來說,持久代大小受到-XX:PermSize和-XX:MaxPermSize兩個參數的限制,而這兩個參數又受到JVM設定的內存大小限制,這就導致在使用中可能會出現持久代內存溢出的問題,因此在Java 8及之後的版本中徹底移除了持久代而使用Metaspace來進行替代。

Metaspace

上面說了,為了避免出現持久代內存溢出的問題,Java 8及之後的版本徹底移除了持久代而使用Metaspace來進行替代。

Metaspace是方法區在HotSpot中的實現,它與持久代最大的區別在於:Metaspace並不在虛擬機內存中而是使用本地內存。因此Metaspace具體大小理論上取決於32位/64位系統可用內存的大小,可見也不是無限制的,需要配置參數。

接著我們模擬一下Metaspace內存溢出的情況,前面說了持久代存放了以下信息:

  • 虛擬機加載的類信息
  • 常量池
  • 靜態變量
  • 即時編譯後的代碼

所以最簡單的模擬Metaspace內存溢出,我們只需要無限生成類信息即可,類佔據的空間總是會超過Metaspace指定的空間大小的,下面用Cglib來模擬:

1 public class MetaspaceOOMTest {

2

3 /**

4 * JVM參數:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m -XX:+PrintFlagsInitial

5 */

6 public static void main(String[] args) {

7 int i = 0;

8

9 try {

10 for (;;) {

11 i++;

12

13 Enhancer enhancer = new Enhancer();

14 enhancer.setSuperclass(OOMObject.class);

15 enhancer.setUseCache(false);

16 enhancer.setCallback(new MethodInterceptor() {

17 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

18 return proxy.invokeSuper(obj, args);

19 }

20 });

21 enhancer.create();

22 }

23 } catch (Exception e) {

24 System.out.println("第" + i + "次時發生異常");

25 e.printStackTrace();

26 }

27 }

28

29 static class OOMObject {

30

31 }

32

33 }

虛擬機參數設置為"-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m",運行代碼,結果為:

1 第15562次時發生異常

2 net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null

3 at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)

4 at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)

5 at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)

6 at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)

7 at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)

8 at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)

9 at org.xrq.commom.test.jvm.MetaspaceOOMTest.main(MetaspaceOOMTest.java:34)

10 Caused by: java.lang.reflect.InvocationTargetException

11 at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)

12 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

13 at java.lang.reflect.Method.invoke(Unknown Source)

14 at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:413)

15 at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)

16 ... 6 more

17 Caused by: java.lang.OutOfMemoryError: Metaspace

18 at java.lang.ClassLoader.defineClass1(Native Method)

19 at java.lang.ClassLoader.defineClass(Unknown Source)

20 ... 11 more

可見即使使用了Metaspace,也是有OOM的風險的,但是由於Metaspace使用本機內存,因此只要不要代碼裡面犯太低級的錯誤,OOM的概率基本是不存在的。

Metaspace相關JVM參數

最後我們來看一下Metaspace相關的幾個JVM參數:

Java虛擬機16:Metaspace


原文:http://www.cnblogs.com/xrq730/p/8688203.html


分享到:


相關文章: