以前已经讲过了很多有关 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 引用。
那具体是如何管理的呢?
元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。
运行时常量池在 JDK6 及以前版本的 JVM 中是方法区的一部分,而在 HotSpot 虚拟机中方法区的实现是永久代(Permanent Generation)。因此运行时常量池也是在永久代的。
可是 JDK7 及以后版本的 JVM 已经将字符串常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。
String.intern()
是一个 Native 方法,它的做用是:若是运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;若是没有,则在常量池中建立与此 String 内容相同的字符串,并返回常量池中建立的字符串的引用。
前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并非固定大小,所以有可能分配的空闲区块和类须要的区块大小不一样,这种状况下可能致使碎片存在。元空间虚拟机目前并不支持压缩操做,因此碎片化是目前最大的问题。
曾经的永久代,由于容易产生 OOM 而被优化成了元空间,但即使这样,依然存在着问题,不知道 JDK 以后还会怎样优化呢?
有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。