Jvm-永久代与元空间

Jvm-永久代与元空间

PermGen是Permanent Generation的缩写,它是HotSpot对于JVM方法区的一种实现,也常被称为永久代。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,32位JVM的默认最大内存大小为64 MB,64位版本为82 MB。永久代的垃圾收集是和老年代(old generation)在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

一个常见的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会抛出OOM异常。特别地是,在Java 7之前,字符串常量池实际上保留在PermGen里,这种情况导致的java.lang.OutOfMemoryError: PermGen space,常会让人产生误解。

移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存
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());
}
}
}

分别在JDK1.6,1.7,1.8下面运行这段代码:JDK 1.6下,会出现”PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出。

永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory(本地内存)中。 默认情况下,类元数据分配仅受可用的本机内存限制,但可以通过以下参数来指定元空间的大小:

  1. -XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值
  2. -XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
  3. -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集
  4. -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集
  5. 默认情况下,class metadata的分配仅受限于可用的native memory总量。可以使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。

Metaspace背后的一个思想是,类和它的元数据的生命周期是和它的类加载器的生命周期一致的。也就是说,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被释放。每个类加载器存储区叫做“a metaspace”。这些metaspaces一起总体称为”the Metaspace”。仅仅当类加载器不在存活,被垃圾收集器声明死亡后,该类加载器对应的metaspace空间才可以回收。Metaspace空间没有迁移和压缩。但是元数据会被扫描是否存在Java引用。

Metaspace VM使用一个块分配器(chunking allocator)来管理Metaspace空间的内存分配。块的大小依赖于类加载器的类型。其中有一个全局的可使用的块列表(a global free list of chunks)。当类加载器需要一个块的时候,类加载器从全局块列表中取出一个块,添加到它自己维护的块列表中。当类加载器死亡,它的块将会被释放,归还给全局的块列表。块(chunk)会进一步被划分成blocks,每个block存储一个元数据单元(a unit of metadata)。Chunk中Blocks的分配线性的(pointer bump)。这些chunks被分配在内存映射空间(memory mapped(mmapped) spaces)之外。在一个全局的虚拟内存映射空间(global virtual mmapped spaces)的链表,当任何虚拟空间变为空时,就将该虚拟空间归还回操作系统。

Metaspace


参考资料:

  • JDK-6962931 : move interned strings out of the perm gen
  • PermGen Elimination project is promoting
  • Where Has the Java PermGen Gone?
-------------本文结束感谢您的阅读-------------
坚持分享,您的支持将鼓励我继续创作!
0%