前言
在自定义类加载器还没有流行的时候,普通的Java对象和类对象都是存放在堆中的,由于类很少被卸载,所以引入了PermGen来存放这些“顽固”的类对象以提高性能。 PermGen中的还存放着类的全限定名(JDK1.7中将字符串常量池移出了永久代)、JIT编译器优化的信息、由JVM创建的内部对象,如Object,Exception等等信息。 但在各种代理类、自定义类加载器层出不穷的今天,当数量足够多的新类型被加载到虚拟机中后,可能很快地导致永久代的空间不足从而产生OOM。
OOP-Klass
在HotSpot实现中,存放在PermGen中的类的静态字段,常量池,方法信息和方法字节码,与之相关联的对象数组和类型数组等元数据信息是由Klass(C++)对象存储的。
一个Java对象在HotSpot VM中由OOP-Klass组成,其中OOP包含
实例数据
对象头
Mark Word,用于标识一些对象的信息
指向Klass对象的指针
++Perhaps a simple example will suffice.++ Consider a Java object instance. The storage for that instance consists of the fields declared by the Java programmer. In addition to those, we add a header, the important part of which, for our purposes here, is a pointer to the representation of the Java class that defined this instance. The representation of the Java class is itself an object (but not a Java object; an “instanceKlass”), containing obvious things like the static fields of the class. But in addition, it includes a description of the types of the fields of every instance of this Java class, so that the storage manager can find and adjust reference fields, for example, if it moves objects in memory. Since the representation of the Java class contains references to other objects, it must itself have a header with a pointer to something (another object, also not a Java object; an “instanceKlassKlass”) that describes its fields. This chain of classes would continue indefinitely if not for the fact that instanceKlassKlass's are described by klassKlass, which can describe itself. ++Perhaps a simple example won't suffice++. There are subclasses of klass for each of the types of object managed by the storage manager.
根据OpenJDK官方的解释,Klass对象保存着Java Class对象中的类的静态字段,常量池,方法信息和方法字节码,与之相关联的对象数组和类型数组等基本信息。但由于GC会导致地址变动,所以对于Java Class中关联的对象(存放于对象数组中),需要有对应的指针去指向对应的Klass对象,即klassKlass指向的对象,依此类推。
为什么要移除永久代
—— Because nothing last permanent.
在JDK1.8中,PermGen在HotSpot中被移除,由Metaspace取代,在JDK1.8 VM参数中配置-XX:PermSize和-XX:MaxPermSize已经不会生效了。移除了PermGen后,先前的类元素据,interned string和静态的类变量将会移动到堆或者本地内存。
为什么要移除PermGen,主要原因有两点。
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.
原因之一是由于Oracle收购了HotSpot和JRockit虚拟机,而在JRockit中是没有永久代的概念的。为了整合两个虚拟机的优势,所以移除了PermGen。
另一个原因则是由于JDK1.8之前的PermGen是JVM内存的一部分,这一部分虽然可以扩展(从PermSize到MaxPermSize),但是始终会受到JVM内存大小(-Xmx)的限制。同时当PermGen容量不足需要扩容时产生的带来的FullGC也是系统的负担。当项目比较庞大,需要加载的类数量很多时,PermGen很容易被消耗殆尽。如果PermGen的大小设置不恰当导致GC后也无法继续分配空间则会造成OOM,所以设置一个比较恰当的PermGenSize对于系统的稳定运行很重要,但这并不好把握。
The Metaspace
结构及内容
Metaspace中存放的类的metadata据包含类的层次结构信息,方法数据和方法信息(如字节码、栈的大小及本地变量表的大小等),运行时常量池,解析过的符号引用及虚函数表。
Metaspace的设计理念是让类和类的元数据的生命周期和加载该类的类加载器生命周期相关,即只要类加载器没有被回收(类卸载的先决条件),那么这个类的元数据就不会被回收。由此Metaspace按照每个类加载器对应一个独立区域来存放,该区域可以称为元空间(metaspace由chunk list组成)
metaspace通过线性(指针碰撞)分配器来分配
存储于metasapce的内容不会被GC扫描也不会被压缩
分配于其中的对象不会被移动位置
不会单独进行回收,只会在GC发现类加载器死亡时统一进行回收
所有的metaspace合起来称为Metaspace。不同于PermGen在JVM内分配,Metaspace是在本地内存中直接分配的,所以其最大的容量限制为本机的内存容量,而且它和堆内存不连续。
上图为Metaspace的结构示意图,每一个ClassLoader(红色)维护着由自己加载的类产生的元块(Metachunks,绿色)生成的链表,这些元块存放于虚拟空间(蓝色)中。虚拟内存之间也是通过链表关联的,由上方的黑色箭头标识。虚拟空间中还可能存在已经被释放的块,但图中未标出。元数据块被释放后回到虚拟空间的可用列表中,虚拟空间不再被使用时,将会回到系统内存中。多个Metaspaces组成宏观上的The Metaspace。
Compressed Class Space
当开启了-XX:+UseCompressedClassesPointers时,会在元块中分出一块独立的区域,用于存放类的klass实例压缩指针,以及类引用的其他类(以类数组的形式存在)的klass实例压缩指针,该空间的默认大小1GB。而class的元数据信息(metadata)则仍然存放于Metaspace中。
注意事项
Metaspace的引入并没有完全解决OOM的问题,因为即便直接在系统内存中分配也是有可能在极端情况下耗尽系统内存的,此时只是将内存耗尽的时间延长了。而且在不设置MaxMetaspaceSize的情况下,如果不及时发现内存泄漏或者类加载的异常,从而导致的系统内存被耗尽的影响只比OOM更为严重。