JVM 知识点补充——永久代和元空间

以前已经讲过了很多有关 JVM 的内容,今天准备将以前没有细讲的部分进行补充,好比:永久代和元空间。 java

永久代

Java 的内存中有一块称之为方法区的部分,在 JDK8 以前, Hotspot 虚拟机中的实现方式为永久代(Permanent Generation),别的JVM都没有这个东西。git

在过去(当自定义类加载器使用不广泛的时候),类几乎是“静态的”而且不多被卸载和回收,所以类也能够被当作“永久的”。另外因为类做为 JVM 实现的一部分,它们不禁程序来建立,由于它们也被认为是“非堆”的内存。github

永久代是一段连续的内存空间,咱们在 JVM 启动以前能够经过设置-XX:MaxPermSize的值来控制永久代的大小,32 位机器默认的永久代的大小为 64M,64 位的机器则为 85M。性能

永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。可是有一个明显的问题,因为咱们能够经过‑XX:MaxPermSize设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误 (java.lang.OutOfMemoryError: PermGen space)。优化

为何类的元数据占用内存会那么大?由于在 JDK7 以前的 HotSpot 虚拟机中,归入字符串常量池的字符串被存储在永久代中,所以致使了一系列的性能问题和内存溢出错误。spa

为了解决这些性能问题,也为了可以让 Hotspot 能和其余的虚拟机同样管理,元空间就产生了。操作系统

元空间

元空间是 Hotspot 在 JDK8 中新加的内容,其本质和永久代相似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:指针

元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元空间的大小仅受本地内存限制,但能够经过如下参数来指定元空间的大小:code

-XX:MetaspaceSize cdn

>初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:若是释放了大量的空间,就适当下降该值;若是释放了不多的空间,那么在不超过MaxMetaspaceSize时,适当提升该值。

  

-XX:MaxMetaspaceSize

>最大空间,默认是没有限制的。

除了上面两个指定大小的选项之外,还有两个与 GC 相关的属性:

-XX:MinMetaspaceFreeRatio

>在GC以后,最小的Metaspace剩余空间容量的百分比,减小为分配空间所致使的垃圾收集

-XX:MaxMetaspaceFreeRatio

>在GC以后,最大的Metaspace剩余空间容量的百分比,减小为释放空间所致使的垃圾收集

移除永久代的影响

因为类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。所以,咱们就不会遇到永久代存在时的内存溢出错误,也不会出现泄漏的数据移到交换区这样的事情。最终用户能够为元空间设置一个可用空间最大值,若是不进行设置,JVM 会自动根据类的元数据大小动态增长元空间的容量。

注意:永久代的移除并不表明自定义的类加载器泄露问题就解决了。所以,你还必须监控你的内存消耗状况,由于一旦发生泄漏,会占用你的大量本地内存,而且还可能致使交换区交换更加糟糕。

元空间内存管理

元空间的内存管理由元空间虚拟机来完成。

先前,对于类的元数据咱们须要不一样的垃圾回收器进行处理,如今只须要执行元空间虚拟机的 C++ 代码便可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,于是不会被回收掉。

准确的来讲,每个类加载器的存储区域都称做一个元空间,全部的元空间合在一块儿就是咱们一直说的元空间。当一个类加载器被垃圾回收器标记为再也不存活,其对应的元空间会被回收。在元空间的回收过程当中没有重定位和压缩等操做。可是元空间内的元数据会进行扫描来肯定 Java 引用。

那具体是如何管理的呢?

元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。

  1. 当一个类加载器须要组块时,它就会从这个全局的组块列表中获取并维持一个本身的组块列表。
  2. 当一个类加载器再也不存活时,那么其持有的组块将会被释放,并返回给全局组块列表。
  3. 类加载器持有的组块又会被分红多个块,每个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式链接,一旦某个虚拟内存映射区域清空,这部份内存就会返回给操做系统。

运行时常量池

运行时常量池在 JDK6 及以前版本的 JVM 中是方法区的一部分,而在 HotSpot 虚拟机中方法区的实现是永久代(Permanent Generation)。因此运行时常量池也是在永久代的。

可是 JDK7 及以后版本的 JVM 已经将字符串常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。

String.intern()是一个 Native 方法,它的做用是:若是运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;若是没有,则在常量池中建立与此 String 内容相同的字符串,并返回常量池中建立的字符串的引用。

存在的问题

前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并非固定大小,所以有可能分配的空闲区块和类须要的区块大小不一样,这种状况下可能致使碎片存在。元空间虚拟机目前并不支持压缩操做,因此碎片化是目前最大的问题。

总结

曾经的永久代,由于容易产生 OOM 而被优化成了元空间,但即使这样,依然存在着问题,不知道 JDK 以后还会怎样优化呢?

有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。

death00.github.io/

相关文章
相关标签/搜索