文档章节

JDK8 Metaspace的引入

此鱼不得水
 此鱼不得水
发布于 2016/03/10 10:48
字数 2678
阅读 74
收藏 2

在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的错误(对于这句话,译者的理解是:32位的JVM默认MaxPermSize是64M,而JDK8里的Metaspace,也可以通过参数-XX:MetaspaceSize 和-XX:MaxMetaspaceSize设定大小,但如果不指定MaxMetaspaceSize的话,Metaspace的大小仅受限于native memory的剩余大小。也就是说永久代的最大空间一定得有个指定值,而如果MaxPermSize指定不当,就会OOM)。

注:在JDK7之前的版本,对于HopSpot JVM,interned-strings存储在永久代(又名PermGen),会导致大量的性能问题和OOM错误。从PermGen移除interned strings的更多信息查看这里

译者注:从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

在JDK7 update 4即随后的版本中,提供了完整的支持对于Garbage-First(G1)垃圾收集器,以取代在JDK5中发布的CMS收集器。使用G1,PermGen仅仅在FullGC(stop-the-word,STW)时才会被收集。G1仅仅在PermGen满了或者应用分配内存的速度比G1并发垃圾收集速度快的时候才触发FullGC。

而对于CMS收集器,通过开启布尔参数-XX:+CMSClassUnloadingEnabled来并发对PermGen进行收集。对于G1没有类似的选项,G1只能通过FullGC,stop the world,来对PermGen进行收集。

永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。

在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory。一些新的flags被加入:
-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集

默认情况下,class metadata的分配仅受限于可用的native memory总量。可以使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。

Native Heap,就是C-Heap。对于32位的JVM,C-Heap的容量=4G-Java Heap-PermGen;对于64位的JVM,C-Heap的容量=物理服务器的总RAM+虚拟内存-Java Heap-PermGen

这里科普下,在Windows下称为虚拟内存(virtual memory),在Linux下称为交换空间(swap space),用于当系统需要更多的内存资源而物理内存已经满了的情况下,将物理内存中不活跃的页转移到磁盘上的交换空间中。

在JDK8,Native Memory,包括Metaspace和C-Heap。

IBM的J9和Oracle的JRockit(收购BEA公司的JVM)都没有永久代的概念,而Oracle移除HotSpot中的永久代的原因之一是为了与JRockit合并,以充分利用各自的特点。

再见,再见PermGen,你好Metaspace

随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

类的元数据信息转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。

此外,在HotSpot中的每个垃圾收集器需要专门的代码来处理存储在PermGen中的类的元数据信息。从PermGen分离类的元数据信息到Metaspace,由于Metaspace的分配具有和Java Heap相同的地址空间,因此Metaspace和Java Heap可以无缝的管理,而且简化了FullGC的过程,以至将来可以并行的对元数据信息进行垃圾收集,而没有GC暂停。

永久代的移除对最终用户意味着什么?

由于类的元数据可以在本地内存(native memory)之外分配,所以其最大可利用空间是整个系统内存的可用空间。这样,你将不再会遇到OOM错误,溢出的内存会涌入到交换空间。最终用户可以为类元数据指定最大可利用的本地内存空间,JVM也可以增加本地内存空间来满足类元数据信息的存储。

注:永久代的移除并不意味者类加载器泄露的问题就没有了。因此,你仍然需要监控你的消费和计划,因为内存泄露会耗尽整个本地内存,导致内存交换(swapping),这样只会变得更糟。

移动到Metaspace和它的内存分配

Metaspace VM利用内存管理技术来管理Metaspace。这使得由不同的垃圾收集器来处理类元数据的工作,现在仅仅由Metaspace VM在Metaspace中通过C++来进行管理。Metaspace背后的一个思想是,类和它的元数据的生命周期是和它的类加载器的生命周期一致的。也就是说,只要类的类加载器是存活的,在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大小的调整和可以使用的工具

正如前面提到了,Metaspace VM管理Metaspace空间的增长。但有时你会想通过在命令行显示的设置参数-XX:MaxMetaspaceSize来限制Metaspace空间的增长。默认情况下,-XX:MaxMetaspaceSize并没有限制,因此,在技术上,Metaspace的尺寸可以增长到交换空间,而你的本地内存分配将会失败。

对于64位的服务器端JVM,-XX:MetaspaceSize的默认大小是21M。这是初始的限制值(the initial high watermark)。一旦达到这个限制值,FullGC将会被触发进行类卸载(当这些类的类加载器不再存活时),然后这个high watermark被重置。新的high watermark的值依赖于空余Metaspace的容量。如果没有足够的空间被释放,high watermark的值将会上升;如果释放了大量的空间,那么high watermark的值将会下降。如果初始的watermark设置的太低,这个过程将会进行多次。你可以通过垃圾收集日志来显示的查看这个垃圾收集的过程。所以,一般建议在命令行设置一个较大的值给XX:MetaspaceSize来避免初始时的垃圾收集。

每次垃圾收集之后,Metaspace VM会自动的调整high watermark,推迟下一次对Metaspace的垃圾收集。

这两个参数,-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,类似于GC的FreeRatio参数,可以放在命令行。

当前的问题

先前提到的,Metaspace VM使用块分配器(chunking allocator)。chunk的大小取决于类加载器的类型。由于类class并没有一个固定的尺寸,这就存在这样一种可能:可分配的chunk的尺寸和需要的chunk的尺寸不相等,这就会导致内存碎片。Metaspace VM还没有使用压缩技术,所以内存碎片是现在的一个主要关注的问题。


本文转载自:http://ifeve.com/java-permgen-removed/

上一篇: String常量池详解
下一篇: OSI7层模型
此鱼不得水
粉丝 2
博文 41
码字总数 23991
作品 0
洛阳
私信 提问
GC之PermGen到Metaspace

JDK8之前,static variables、class metadata存放在PermGen(Permanent Generation)中,JDK8上,static variables、class metadata存放在MetaSpace中。为什么要移除PermGen? PermGen 的缺点:......

汉斯-冯-拉特
2018/07/01
0
0
JVM的方法区和永久带是什么关系?

群里面有小伙伴问到这个问题,说在网上看了很多文章,但是还是没弄明白这俩是啥关系,下面我们就来详细的解释一下: 什么是方法区? 方法区(Method Area)是jvm规范里面的运行时数据区的一个...

若鱼1919
2018/07/26
0
0
Metaspace && Heap space的内存溢出

Metaspace && Heap space的内存溢出 note that when the heap space runs out you get However when you run out of PermGen you get However in jdk8 when you run out of Metaspace you......

秋风醉了
2015/11/23
810
0
PerfMa给OpenJDK社区提交的第一个Patch

概述 前两天给openjdk gc-dev的email list提交了一个问题,主要是针对Full GC之后,GC日志里Metaspace的大小在GC前后都一直不变的问题,我在邮件里大概也提了下如何修复该问题,以及猜测了下...

你假笨
2018/09/25
0
0
JDK8-废弃永久代(PermGen)迎来元空间(Metaspace)

转自:https://www.cnblogs.com/dennyzhangdd/p/6770188.html 目录 1.背景 2.为什么废弃永久代(PermGen) 3.深入理解元空间(Metaspace) 4.总结 ========正文分割线===== 一、背景 1.1 永久...

yntmdr
2017/12/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

干货 | 解读MySQL 8.0新特性:Skip Scan Range

MySQL从8.0.13版本开始支持一种新的range scan方式,称为Loose Skip Scan。该特性由Facebook贡献。我们知道在之前的版本中,如果要使用到索引进行扫描,条件必须满足索引前缀列,比如索引idx...

迷你芊宝宝
29分钟前
1
0
观点 | 云原生时代来袭 下一代云数据库技术将走向何方?

全面云化的时代已经到来,面对一系列的新技术和挑战,数据库市场将面临怎样的变革?作为云服务提供商,如何帮助更多的企业级用户把握“云”潮,提供最高效、最具价值的数据库解决方案? 日前...

zhaowei121
38分钟前
1
0
ReentrantLock是如何基于AQS实现的

ReentrantLock是一个可重入的互斥锁,基于AQS实现,它具有与使用 synchronized 方法和语句相同的一些基本行为和语义,但功能更强大。 lock和unlock ReentrantLock 中进行同步操作都是从lock方...

java菜分享
39分钟前
1
0
比特币钱包开发【C#】

在这个教程中,我们将使用C#来开发一个比特币钱包,我们使用NBitcoin这个库。教程中的代码实现了比特币的存储、接收和支付功能,可以很容易地移植到其他应用中。 如果要快速掌握在C#程序中N...

汇智网教程
39分钟前
1
0
centos7.4编译安装nginx

1、安装准备环境 yum install gcc gcc-c++ automake pcre pcre-devel zlip zlib-devel openssl openssl-devel pcre* 下载pcre wget https://jaist.dl.sourceforge.net/project/pcre/pcre/8.......

Marhal
41分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部