Java HotSpot 虚拟机是 Java SE 平台的一个核心组件。它实现 Java 虚拟机规范,并作为 Java 运行时环境中的一个共享库来提供。作为 Java 字节码执行引擎,它在多种操作系统和架构上提供 Java 运行时设施,如线程和对象同步。它包括自适应将 Java 字节码编译成优化机器指令的动态编译器,并使用为降低暂停时间和吞吐量而优化的垃圾收集器来高效管理 Java 堆。它为分析、监视和调试工具及应用程序提供数据和信息。
最近在研究它在1.8中的新特性:
- JAVA 从永久区(PermGen)到元空间(Metaspace)
JAVA 从永久区(PermGen)到元空间(Metaspace)
1. 永久代
在说java8内存模型之前先说一下永久代的概念。
在Java虚拟机(JVM)内部,class文件中包括类的版本、字段、方法、接口等描述信息,还有运行时常量池,用于存放编译器生成的各种字面量和符号引用。
在过去类大多是”static”的,很少被卸载或收集,因此被称为“永久的(Permanent)”。
同时,由于类class是JVM实现的一部分,并不是由应用创建的,所以又被认为是“非堆(non-heap)”内存。
在JDK8之前的HotSpot JVM,存放这些”永久的”的区域叫做“永久代(permanent generation)”。
永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小是64M(64位JVM由于指针膨胀,默认是85M)。
永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误
2. Metaspace(元空间)
jdk1.8中则把永久代给完全删除了,取而代之的是 MetaSpace
2.1 metaspace的组成
metaspace其实由两大部分组成
- Klass Metaspace
- NoKlass Metaspace
Klass Metaspace就是用来存klass的,klass是我们熟知的class文件在jvm里的运行时数据结构,不过有点要提的是我们看到的类似A.class其实是存在heap里的,是java.lang.Class的一个对象实例。
这块内存是紧接着Heap的,和我们之前的perm一样,这块内存大小可通过-XX:CompressedClassSpaceSize参数来控制,这个参数前面提到了默认是1G,但是这块内存也可以没有,假如没有开启压缩指针就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里,另外如果我们把-Xmx设置大于32G的话,其实也是没有这块内存的,因为这么大内存会关闭压缩指针开关。
还有就是这块内存最多只会存在一块。
NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool(常量池)等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。
这块内存是必须的,虽然叫做NoKlass Metaspace,但是也其实可以存klass的内容,上面已经提到了对应场景。
Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。
如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。
2.2 Metaspace的内存分配与管理
Metaspace VM利用内存管理技术来管理Metaspace。
这使得由不同的垃圾收集器来处理类元数据的工作,现在仅仅由Metaspace VM在Metaspace中通过C++来进行管理。
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)的链表,当任何虚拟空间变为空时,就将该虚拟空间归还回操作系统。
2.3 Metaspace VM内存碎片问题
先前提到的,Metaspace VM使用块分配器(chunking allocator)。
chunk的大小取决于类加载器的类型。
由于类class并没有一个固定的尺寸,这就存在这样一种可能:
可分配的chunk的尺寸和需要的chunk的尺寸不相等,这就会导致内存碎片。
Metaspace VM还没有使用压缩技术,所以内存碎片是现在的一个主要关注的问题。
2.4 Metaspace 总结
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
不过元空间与永久代之间最大的区别在于:
元空间并不在虚拟机中,而是使用本地内存。
因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小。
2.5 MetaSpace应该掌握的知识
- 在 JDK 1.7 和 1.8 中将字符串常量池由永久代转移到堆中
- 存放类相关信息的地方也不在heap(堆)中。在元空间里。
- 在jdk1.8中没有永久代的概念
- metaspace其实由两大部分组成
- Klass Metaspace
存放klass的,klass是我们熟知的class文件在jvm里的运行时数据结构,这个空间的默认大小是1G- NoKlass Metaspace
专门来存klass相关的其他的内容,比如method,constantPool(常量池)等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。
如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。
5.metaspace主要相关参数
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:
- 如果释放了大量的空间,就适当降低该值;
- 如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
- Metaspace的内存分配与管理 都应该清楚
- 为什么要将永久代替换成Metaspace?
- 字符串存在永久代中,容易出现性能问题和内存溢出。
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
- Oracle 可能会将HotSpot 与 JRockit 合二为一。
参见:
Java 8新特性探究(九)跟OOM:Permgen说再见吧:http://www.importnew.com/14933.html
JVM(二)Java8内存划分:https://blog.csdn.net/yjp198713/article/details/78759933
JVM源码分析之Metaspace解密:http://www.cnblogs.com/paddix/p/5309550.html