根据 JVM 规范,JVM 运行时区域大致分为 方法区、堆、虚拟机栈、本地方法栈、程序计数器 五个部分。
1)方法区
方法区是JVM 所有线程共享。
主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与 堆 进行区分,通常又叫 非堆。 关于 方法区内存溢出 的问题会在下文中详细探讨。
2)堆
堆内存也是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError 。
1、PermGen(永久代)
方法区 是 JVM 的规范,所有虚拟机 必须遵守的。常见的JVM 虚拟机 Hotspot 、 JRockit(Oracle)、J9(IBM)
PermGen space 则是 HotSpot 虚拟机 基于 JVM 规范对 方法区 的一个落地实现, 并且只有 HotSpot 才有 PermGen space。
而如 JRockit(Oracle)、J9(IBM) 虚拟机有 方法区 ,但是就没有 PermGen space。
PermGen space 是 JDK7及之前, HotSpot 虚拟机 对 方法区 的一个落地实现。在JDK8被移除。
Metaspace(元空间)是 JDK8及之后, HotSpot 虚拟机 对 方法区 的新的实现。
JDK6、JDK7 时,方法区 就是 PermGen(永久代)。
JDK8 时,方法区就是 Metaspace(元空间)
由于方法区 主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。
2、Metaspace(元空间)
Metaspace(元空间)和 PermGen(永久代)类似,都是对 JVM规范中方法区的一种落地实现。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
Oracle 移除PermGen(永久代)从从JDK7 就开始。例如,字符串内部池,已经在JDK7 中从永久代中移除。直到JDK8 的发布将宣告 PermGen(永久代)的终结。
其实,移除 PermGen 的工作从 JDK7 就开始,永久代的部分数据就已经转移到了 Java Heap 或者是 Native Heap。
但永久代仍存在于JDK7 中,并没完全移除,比如:
字面量 (interned strings)转移到 Java heap;
类的静态变量(class statics)转移到Java heap ;
符号引用(Symbols) 转移到 Native heap ;
必须知道的是 JDK6 、JDK7 依然存在 PermGen space;
我们可以通过一段程序来比较 JDK6 、 JDK7 和 JDK8 的区别,以字符串常量为例:
3、JDK6 、JDK7、JDK8 内存溢出的示例
import java.util.ArrayList;
import java.util.List;
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());
}
}
}
JDK6 的运行结果:
JDK7 的运行结果:
JDK8 的运行结果:
从运行结果可以得出:
1)运行时常量池 :
在 JDK6 ,抛出永久代(PermGen space)异常,说明 运行时常量池 存在于 方法区;
在 JDK7、JDK8 抛出堆(Java heap space)异常,说明 运行时常量池 此时在 Java堆 中;