一、方法区(Method Area)
JVM虚拟机规范中虽然在逻辑上将方法区描述为堆区的一部分,但对于HotSpot虚拟机而言,还有一个别名Non-Heap(非堆),目的是与堆区分开。方法也是线程共享的区域,在JVM启动的时候被创建,并且,和堆区一样可以是逻辑上连续,物理上不连续的区域。
方法区包含了四部分内容;
类型信息:类的版本、接口、字段、方法等描述信息。
运行时常量池:Class文件加载后的常量池数据(各种字面量和符号引用),直接引用,字符串常量池(JDK8移入Java堆中)。字面量:各种文本字符串、final常量值、基本类型数据数值等,如描述Object类的"()Ljava/lang/Object"字符串就是一个字面量。符号引用:如cp info #44 <toString>
,#44就是对toString方法的符号引用。
JIT代码缓存:即时编译器编译后的代码缓存。
静态变量:类中的静态变量,JDK7以后移入Java堆中,并且将静态变量存在类型数据对应的java.lang.Class实例里面,下面是引自《深入理解Java虚拟机》的图。
二、方法区实现的变化
JDK8以前,很多人把方法区称为永久代(Permanent Generation),实际上两者并不等价,这一说法是因为HotSpot虚拟机将垃圾收集分代设计将区域扩展到方法区,或者说永久代是方法区的实现。由于永久代更容易OOM,所以该设计在JDK7以后逐渐被淘汰,JDK1.7以后将字符串常量池和静态变量移入堆区,在JDK8以后,就不再有永久代说法,而是元空间(Meta Space)实现方法区。元空间和永久代最大的区别就是元空间不在JVM内存之中,而是使用本地内存,这样可以大大降低方法区的OOM。
字符串常量池被移到Java堆的原因?
方法区的垃圾回收只有在Full GC时才会发生,又因为String字符串创建频繁,放回堆中,使得能够及时回收。
三、方法区大小设置
1、永久代(JDK1.7)
- -XX:PermSize : 永久代初始化内存,默认20.75M, -XX:PermSize=20m
- -XX:MaxPermSize: 永久代最大内存,32位虚拟机默认64M, 64位默认82M,超出时会报java.lang.OutOfMemoryError:PermaGen space
2、元空间(JDK1.8)
- -XX:MetaspaceSize:元空间初始化内存,默认是21M
- -XX:MaxMetaspaceSize:元空间最大内存,默认是-1无限制。虽然使用直接内存,但是当无法再为Metaspace分配内存时,将会抛出java.lang.OutOfMemoryError: Metaspace
四、方法区OOM
1、字符串常量池溢出导致OOM
基于JDK1.6,字符串常量池在永久代之中,-XX:MaxPermSize=6m限制永久代最大内存为6M,执行以下代码,将会出现java.lang.OutOfMemoryError: PermaGen space
public class PermGenOOMDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
short i = 0;
while (true) {
set.add(String.valueOf(i++).intern());
}
}
}
JDK1.7以后字符串常量池进入Java堆,执行以上代码就不会出现OOM,而限制最大堆内存-Xmx之后,将同样出现OOM,只不过错误变成java.lang.OutOfMemoryError: Java heap space。
2、类型数据加载导致OOM
类型数据是方法区的主要组成部分,无论JDK1.6、JDK1.7或是1.8在设置永久代上限或元空间上限的情况下,都会出现OOM,如下代码,引入CGLib依赖,不断创建动态代理类放入方法区:
- 当JVM参数为-XX:MaxPermSize=6m时,抛出异常java.lang.OutOfMemoryError: PermaGen space
- 当JVM参数为—XX:MaxMetaspaceSize=6m时,抛出异常java.lang.OutOfMemoryError: Metaspace
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class MethodOOM {
static class OOMObject {
}
public static void main(final String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
}
}