永久代退出舞台,元空间腾空出世

Posted 爱奇志

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了永久代退出舞台,元空间腾空出世相关的知识,希望对你有一定的参考价值。

一、前言

二、元空间由来和本质

2.1 元空间的诞生与永久代的消失

在JDK8中,永久代这部分内存空间被全部移除,一种新的内存空间的诞生。HotSpot JVM 使用本地内存来存储类元数据信息并称之为:元空间(Metaspace),这与Oracle JRockit 和IBM JVM很相似。

这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要进行调优及监控内存空间的使用,但是新特性不能消除类和类加载器导致的内存泄漏,仍然需要使用不同的方法以及遵守新的命名约定来追踪这些问题。

2.2 从永久代到元空间

2.2.1 JDK8之前,永生代在哪里?

先上图,hotspot jvm结构如下(虚拟机栈和本地方法栈合一起了):

问题:jdk8之前,永生代在哪里?

回答:如上图的HotSpot JVM中,永久代就是上图的方法区(只是JVM规范中称为方法区)。《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它,同时,除了HotSpot虚拟机,其他JVM上也不存在永久代。

2.2.2 JDK8之后,永生代废弃,元空间诞生

问题:JDK8发生了哪些变化?

回答:

1.新生代:还是Eden+From Survivor+To Survivor,没有变化;

2.老年代:还是OldGen,没有变化;

3.永久代(方法区) : PermGen(虚拟机中)----->Metaspace(本地内存中),即元数据区代替永久代。

黄金两句:

第一,元空间的本质和永久代类似,都是对JVM规范中方法区在HotSpot jvm 中的实现:方法区主要用于存储类的信息、常量池、方法数据、方法代码等,方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

第二,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存:理论上取决于32位/64位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。

2.3 元空间详情

2.3.1 元空间内存分配模型

元空间和永久代最大区别:大部分类元数据都在本地内存中分配,用于描述类元数据的“klasses”已经被移除。

2.3.2 元空间容量

默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。

新参数MaxMetaspaceSize可以用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。

2.3.3 元空间垃圾回收

对于将死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。

适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。

2.3.4 Java堆内存的影响

一些杂项数据已经移到Java堆空间中,升级到JDK8之后,会发现Java堆 空间有所增长。

2.3.5 元空间监控

元空间的使用情况可以从HotSpot1.8的详细GC日志输出中得到。

Jstat 和 JVisualVM两个工具,在我们使用b75版本进行测试时,已经更新了,但是还是能看到老的PermGen空间的出现。

2.4 更新原因

1、官方原因:Oracle 可能会将HotSpot 与 JRockit 合二为一(官方解释:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代)。

2、现实原因:OutOfMemoryError: PermGen:字符串存在永久代中,永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。

3、现实原因:类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

4、现实原因:永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

三、永久代参数配置 + 元空间参数配置

3.1 永久代参数配置

1. 与永久代有关的两个参数

PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。

2. 元空间相关参数设置

默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:两个参数 MetaspaceSize 和 MaxMetaspaceSize。

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

3.2 元空间参数配置

1.MetaspaceSize:初始化元数据空间

第一,初始化元数据空间:控制元空间发生GC的阈值,达到该值就会触发垃圾收集进行类型卸载;

第二,GC会对该值进行调整:GC后,动态增加或降低MetaspaceSize,原则是:同时如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用-XX:+PrintFlagsInitial命令查看本机的初始化参数。

2.MaxMetaspaceSize:最大元数据空间

最大元数据空间,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

3.MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。

4.MaxMetaspaceFreeRatio:在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。

5.MaxMetaspaceExpansion

Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。

6.MinMetaspaceExpansion

Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。

四、永久代监控测试 + 元空间监控测试

4.1 代码

public class StringOomMock {
    static String  base = "string";

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i=0;i< Integer.MAX_VALUE;i++){
            String str = base + base;
            base = str;
            list.add(str.intern());
        }
    }
}

4.2 参数配置

run configuration–>VM Options参数如下:

-Xms20m -Xmx20m -XX:PermSize=8m -XX:MaxPermSize=8m

4.3 输出结果

由于设定了最大内存20M,很快就溢出,如下图:

从运行结果中,可以知道,JDK8中:

1.字符串常量由永久代转移到堆中;

2.永久代已不存在,PermSize MaxPermSize参数已移除(看图中最后两行)。

五、尾声

永久代退出舞台,元空间腾空出世,完成了。

天天打码,天天进步!!!

以上是关于永久代退出舞台,元空间腾空出世的主要内容,如果未能解决你的问题,请参考以下文章

方法区永久代元空间

java 8中撤销永久代,引入元空间

20210608 由永久代到元空间

20210608 由永久代到元空间

Java8内存模型—方法区 (old:永久代 new:元空间)

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