JVM永久代(PernGem)和元空间(Metaspace)

来源:互联网 发布:淘宝流量下降的原因 编辑:程序博客网 时间:2024/06/06 09:36

前言

在之前的文章中提到过方法区存放的是虚拟机已经加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。同时运行时常量池也是方法区的一部分。但是在不同的JDK版本中对方法区的实现方式存在一定的差异。

正题

下面通过对JDK1.6、JDK1.7、JDK1.8的运行时常量池的对比来看看各自的实现方式。

JDK1.6

在JDK1.6及之前的版本中(针对HotSpot虚拟机),由于常量池分配在永久代内。

public class RuntimeConstantPoolOOM {public static void main(String[] args) {List<String> list = new ArrayList<String>();int i = 0;while(true){System.out.println(i);list.add(String.valueOf(i++).intern());}}}
JDK版本号:
java version "1.6.0_20"Java(TM) SE Runtime Environment (build 1.6.0_20-b02)Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space    at java.lang.String.intern(Native Method)    at com.access.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:14)

从运行结果中可以看出,运行时常量池溢出,在OutOfMemoryError后面跟随的提示信息是“PermGen spacer”,说明运行时常量池属于方法区(HotSpot虚拟机中的永久代)的一部分。

通过使用Java VisualVM工具观察PermGen内存运行轨迹:


从上图可以看出:因为程序通过使用列表保存在PernGem区域中字符串的引用,导致PermGen区域的内存不断飙升直至抛出OutOfMemoryError异常。

如果不把String对象add到list列表的情况:


从上图可以看出:只要PermGen的使用量接近或达到最大大小时就会触发PermGen区域的垃圾回收。

JDK1.7

JDK版本号:

java version "1.7.0_79"Java(TM) SE Runtime Environment (build 1.7.0_79-b15)Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

通过使用Java VisualVM工具观察PernGen内存运行轨迹图:


从PernGen运行轨迹看出:PernGen很平稳,说明常量池没有分配到PernGen内存区域中。

通过使用Java VisualVM工具观察堆内存运行轨迹图:


从堆内存运行轨迹可以看出:堆不不断增加,最后导致抛出OutOfMemoryError异常。从上图可以知道常量池是在堆上分配存储空间。

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at java.util.Arrays.copyOf(Unknown Source)    at java.util.Arrays.copyOf(Unknown Source)    at java.util.ArrayList.grow(Unknown Source)    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)    at java.util.ArrayList.add(Unknown Source)    at com.access.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:14)

从运行结果可以看出,JDK1.7把常量池从永久代移动到Java堆中,所以在Exception中提示heap溢出。

JDK1.8

随着JDK8的到来,JVM不在有PermGen。但类的元数据信息(metadata)还在,只不过不在是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限的,需要配置参数。

现在大多数的类元数据分配在本地化内存中。默认情况下,类元数据分配受到可用的本机内存容量的限制(容量依然取决于你使用的是32位JVM还是64位操作系统的虚拟内存的可用性。在我的机子上的可用内存是1082130432B)。

类的元数据信息转移到Metaspace的原因:

字符串存放在永久代中,容易出现性能问题和内存溢出。

PermGen这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻。

常用配置参数:

1:MetaspaceSize

初始化Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的品台在12M到20M浮动,使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数

2:MaxMetaspaceSize

限制Metaspace增长上限,防止因为某些情况导致Metaspace无限使用本地内存,影响到其他程序。

3:MinMetaspaceFreeRatio

当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲空间占比过大,内存不够用),那么虚拟机将增加Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长速度,太小的值会导致Metaspace增长缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长过快,浪费内存。

4:MaxMetaspaceFreeRatio

当进行Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机就会释放Metaspace的部分参数。默认值为70,也就是70%。

5:MaxMetaspaceExpansion

Metaspace增长时的最大幅度。

6:MinMetaspaceExpansion

Metaspace增长时最小幅度。

在eclipse中选中类设置参数:-Xms20m -Xmx20 -XX:PermSize=8m -XX:MaxPermSize=8M

运行时会提示:

ClassA类

package com.memory;public class ClassA {}
把ClassA编译成class文件,运行下面程序。

package com.memory;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;public class OOMTest {public static void main(String[] args) {          try{              //准备url              URL url = new File("D:/eclipse/javaEE/WebWorkSpace/ClassProject/src").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("com.memory.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=512m -XX:PermSize=8m -XX:MaxPermSize=8M

Metaspace空间的运行轨迹:


可以从上面的JVisualVM的截图看出:当加载类的总数到达87386之后,就会出现Metaspace区域溢出。也可以通过观察垃圾回收活动轨迹了解元空间内存耗尽。Java程序运行结果:

total: 87416active: 87416unloaded: 0Exception in thread "main" java.lang.OutOfMemoryError: Metaspaceat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(Unknown Source)at java.security.SecureClassLoader.defineClass(Unknown Source)at java.net.URLClassLoader.defineClass(Unknown Source)at java.net.URLClassLoader.access$100(Unknown Source)at java.net.URLClassLoader$1.run(Unknown Source)at java.net.URLClassLoader$1.run(Unknown Source)at java.security.AccessController.doPrivileged(Native Method)at java.net.URLClassLoader.findClass(Unknown Source)at java.lang.ClassLoader.loadClass(Unknown Source)at java.lang.ClassLoader.loadClass(Unknown Source)at com.memory.OOMTest.main(OOMTest.java:26)Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=8m; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=8M; support was removed in 8.0

阅读全文
0 0
原创粉丝点击