Java 8从永久代到metaspace

时间:2022-06-17 22:58:23
转载自:http://blog.csdn.net/chenleixing/article/details/48286127
              http://blog.csdn.net/wang8118/article/details/45765869
              http://lovestblog.cn/blog/2016/10/29/metaspace/

       Java 8完全移除了永久代(PermGen),自从Oracle公司发布了JDK1.7后就已经宣布了这个决定。还有比如内部字符串,从JDK1.7开始就从持久代移除了,JDK8的发布彻底废除了它。Metaspace成为了持久代的继任者。
      这项改动是很有必要的,因为对永久代进行调优是很困难的。永久代中的元数据可能会随着每一次Full GC发生而进行移动。并且为永久代设置空间大小也是很难确定的,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等。
       同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

       JDK8 HotSpot JVM现在使用了本地内存(与堆不相连的本地内存区域)来存储类元数据,被称为Metaspace,和Oracle JRockit以及IBM JVM类似。
       它意味着java.lang.OutOfMemoryError:PermGen space问题会越来越少,也不再需要去调整和监控内存空间。然而这种变化默认是可不见的,接下来我们给你展示的,是你仍然需要关注类元数据内存占用。 请记住,这些新特点并不会很神奇的消除类和类加载器的内存泄露。你需要使用不同的方法和学习新的命名约定来找出问题的根源。

       持久代这块内存区域被完全移除,PermSize和MaxPermSize JVM 参数会被忽略,并且在启动的时候会给出警告信息。
       Metaspace容量:默认的,元数据分配限制于可用的本地内存 (容量大小依赖于操作系统可用虚拟内存)。可以使用-XX:MaxMetaspaceSize来指定容量,允许限制用于类元数据的本地内存大小,这里要注意和MaxPermSize的区别,MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来,而MaxPermSize是会分配一块这么大的内存的。如果没有指定这个标记,Metaspace会根据运行时应用程序的需求来动态的控制大小。
       Metaspace垃圾收集:一旦类元数据的使用量达到了“MaxMetaspaceSize”指定的值,对于无用的类和类加载器,垃圾收集此时会触发。为了控制这种垃圾收集的频率和延迟,合适的监控和调整Metaspace非常有必要。过于频繁的Metaspace垃圾收集是类和类加载器发生内存泄露的征兆,同时也说明你的应用程序内存大小不合适,需要调整。
       Java堆空间影响:一些杂项数据被移到了Java堆空间。
       Metaspace监控:Metaspace 的使用可以通过HotSpot 1.8的详细的GC日志输出观察到。 

元空间内存管理

       元空间的内存管理由元空间虚拟机来完成。先前,对于类的元数据需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的C++代码即可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉。
       准确的来说,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。在元空间的回收过程中没有重定位和压缩等操作。但是元空间内的元数据会进行扫描来确定 Java引用。
       元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。当一个类加载器不再存活,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟 内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。

元空间调优与工具

       正如上面提到的,元空间虚拟机控制元空间的增长。对于一个64位的服务器端JVM来说,其默认的–XX:MetaspaceSize值为21MB。这就是初始的高水位线。一旦触及到这个水位线,Full GC将会被触发并卸载没有用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,这个高水位线则上升。如果释放空间过多,则高水位线下降。如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志我们可以观察到Full GC多次调用。为了避免频繁的GC,建议将–XX:MetaspaceSize设置为一个相对较高的值。
       经过多次GC之后,元空间虚拟机自动调节高水位线,以此来推迟下一次垃圾回收到来。
       有这样两个选项 ‑XX:MinMetaspaceFreeRatio和‑XX:MaxMetaspaceFreeRatio,他们类似于GC的FreeRatio选项,用来设置元空间空闲比例的最大值和最小值。

存在的问题

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

-------------------------------------------------------------------------------------------------------------

Metaspace的组成

       Metaspace其实由两大部分组成
       --- Klass Metaspace
       --- NoKlass Metaspace
       Klass Metaspace就是用来存klass的,klass就是class文件在jvm里的运行时数据结构。这块内存是紧接着Heap的,这块内存大小可通过-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是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。

jstat里的metaspace字段

       通过jstat可以看到metaspace相关的这么一些指标,分别是M,CCS,MC,MU,CCSC,CCSU,MCMN,MCMX,CCSMN,CCSMX。

MC & MU & CCSC & CCSU
    • MC表示Klass Metaspace以及NoKlass Metaspace两者总共committed的内存大小,单位是KB,虽然从上面的定义里我们看到了是capacity,但是实质上计算的时候并不是capacity,而是committed,这个是要注意的。
    • MU这个无可厚非,说的就是Klass Metaspace以及NoKlass Metaspace两者已经使用了的内存大小。
    • CCSC表示的是Klass Metaspace的已经被commit的内存大小,单位也是KB。
    • CCSU表示Klass Metaspace的已经被使用的内存大小。

M & CCS
    • M表示的是Klass Metaspace以及NoKlass Metaspace两者总共的使用率,其实可以根据上面的四个指标算出来,即(CCSU+MU)/(CCSC+MC)。
    • CCS表示的是NoKlass Metaspace的使用率,也就是CCSU/CCSC算出来的。
    PS:所以我们有时候看到M的值达到了90%以上,其实这个并不一定说明metaspace用了很多了,因为内存是慢慢commit的,所以我们的分母是慢慢变大的,不过当我们committed到一定量的时候就不会再增长了。

MCMN & MCMX & CCSMN & CCSMX
    • MCMN和CCSMN这两个值大家可以忽略,一直都是0。
    • MCMX表示Klass Metaspace以及NoKlass Metaspace两者总共的reserved的内存大小,比如默认情况下Klass Metaspace是通过CompressedClassSpaceSize这个参数来reserved 1G的内存,NoKlass Metaspace默认reserved的内存大小是2* InitialBootClassLoaderMetaspaceSize。
    • CCSMX表示Klass Metaspace reserved的内存大小。
综上所述,其实看metaspace最主要的还是看MC,MU,CCSC,CCSU这几个具体的大小来判断metaspace到底用了多少更靠谱。