JVM 永久代和元空间

2022/02/04 posted in  JVM

简单介绍

永久代是JDK7及以前对方法区的一个JVM实现。主要存放的是类信息、字符串常量、静态常量等(PS:JDK7之后比如字符串常量池等信息移到了堆内,不在永久代存储了)

元空间是JDK8之后才有,目的是取代永久代。

区别

对比项 永久代PermGen 元空间Metaspace
支持JDK版本 JDK7及以前 JDK8及以后
JVM参数 -XX:PermSize 初始永久代大小
-XX:MaxPermSize : 最大永久代大小。默认32位64M,64位82M(指针膨胀)
注意:MaxPermSize设置,jvm启动的时候就会申请MaxPermSize大小的内存。
-XX:MetaspaceSize,初始空间配额,单位bytes
-XX:MaxMetaspaceSize,分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比。
注意:MaxMetaspaceSize设置,jvm启动的时候不会申请这么大一块内存,与MaxPermSize是有区别的
OOM 受限于默认值大小,易产生OOM。虽然有参数可以指定永久代大小,但是因为存储内容较多,比较难预估出来一个合适的值。 受限于本机内存大小,OOM概率小。
GC 永久代和老年代GC绑定到一起,二者其一满了之后便可触发永久代和老年代的垃圾回收
JDK7以后,如果使用G1垃圾回收器,永久代的垃圾回收只有在FullGC的时候才会触发。G1仅仅在PermGen满了或者应用分配内存的速度比G1并发垃圾收集速度快的时候才触发FullGC (可参见Oracle博客说明 及文末引用地址说明)
CMS回收器下,可以使用-XX:+CMSClassUnloadingEnabled 参数来让永久代的垃圾回收可以在CMC并发回收阶段处理掉。这点和G1不同。
当metaspace使用达到最大值的时候,会自动触发GC。但不影响堆上GC。
归属 不属于堆内存,但内存空间和堆内存相连(存疑 不属于堆内存,属于Native Memory,受限于服务器内存
补充 虽然可能OOM但不至于导致服务器出现问题(因为有永久代大小限制) 内存默认无上限,受限于服务器内存,所以可能会导致服务器出现问题,需要监控Metaspace的使用情况

移除永久代原因

  1. 减少永久代OOM的情况。

    永久代存放的一些类信息等,在动态生成类之前基本是足够的,但是随着动态类生成的技术的发展,原本的类信息量及大小变的开始不再那么永久不变更了。另外最开始的永久代里还存放字符串常量池,这块的增长也不可控,所以也就可能出现OOM的情况。(PS:JDK7已经开始了字符串常量池、符号引用、静态变量从永久代的迁出)

  2. 降低GC复杂度

    永久代和老年代的GC绑定到一起,但是永久代的内容被GC的可能性要小于老年代,所以,对GC而言,一方面效率不高,另外一方面,复杂度也高了很多(metaspace上gc不需要做压缩和扫描)。

  3. 商业合并

    这个应该是最主要的原因。 现有主流的JVM实现中,除了Hotspot之外,基本没有永久代这个概念,但仍旧运行的很好,如 JRockit。之后Oracle收购了Jrockit之后,便开始了Hotspot和JRockit的合并(非代码合并,而是取了JRockit优点做开发)。于是,在JDK8之后,便取消掉了永久代。

PS:

方法区 不等同于 永久代。 永久代是对方法区的一种实现。

永久代和堆逻辑上是分开的。 一般我们称永久代叫“非堆”,但物理上内存是相连的。

关于永久代和堆的关系
  1. 永久代在不在堆里?

    各有各的说法,各有各的理论。 我理解是内存相连,但逻辑分开。

    推荐看几篇文章了解下各自观点

    方法区的Class信息,又称为永久代,是否属于Java堆? - 毛海山的回答 - 知乎 https://www.zhihu.com/question/49044988/answer/113961406

    https://stackoverflow.com/questions/41358895/permgen-is-part-of-heap-or-not

    https://stackoverflow.com/questions/2129044/java-heap-terminology-young-old-and-permanent-generations/2129294#2129294

    https://stackoverflow.com/questions/1279449/what-is-perm-space

    https://stackoverflow.com/questions/4848669/perm-space-vs-heap-space

  2. Xmx不包含永久代大小

测试

测试代码:

public class PermGenTest {

    private final static int ONE_MB = 1024*1024;

    /**
     * JVM参数: -xmx20m -xms20m -xmn10m -XX:MaxPermSize=30m -XX:PermSize=30m -XX:PretenureSizeThreshold=100
     * @param args
     */
    public static void main(String[] args) {


        //连续申请3M内存
        for (int i =0 ; i < 100; i ++){
            allocateMemory(3);
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    private static void allocateMemory(int size){

        for (int i =0 ; i < size ; i++){
            byte[] bytes = new byte[ONE_MB];
        }

    }

}

1、测试老年代和永久代任意一方满了之后,会触发永久代垃圾回收。

测试环境: JDK1.6/1.7 + CMS垃圾回收 (以JDK1.6为例,1.7的结果与下面类似)

关于代码的说明:

使用-XX:PretenureSizeThreshold=100限定只要对象大小超过100字节,就会直接分配到老年代。代码中,我们连续申请3M大小内存,所以很快会触发CMS的老年代垃圾回收。

JVM参数如下:

/opt/soft/jdk/jdk1.6.0_45/bin/java -verbose:gc -Xloggc:gc.l -XX:+PrintGCDetails -Djava.rmi.server.hostname=192.168.1.x -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=33306 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Xmx20m -Xmn10m -Xms20m -XX:MaxPermSize=30m -XX:PermSize=30m -XX:PretenureSizeThreshold=100 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC  PermGenTest

产生的GC结果:

image-20200430180500797

可以看到,当老年代触发CMS垃圾回收的时候,永久代也会被回收(PS:图中永久代没有可被回收的东西,所以数值没变化)

如果我们把-XX:PretenureSizeThreshold调整成5M,则对象不会进入到老年代,这时候年轻代GC,并没有同时触发对永久代的GC。(如下图,可以看到没有Perm的回收日志)

image-20200430181002059

Ps:

-XX:MaxTenuringThreshold只对串行回收器和ParNew有效,对ParallGC无效

2、测试G1和CMS的永久代垃圾回收策略

测试环境: JDK1.7 + G1垃圾回收 (CMS的已在上面做过测试,此处查看G1和CMS触发垃圾回收场景)

代码不变,JVM参数调整为G1垃圾回收器

JVM参数:

/opt/soft/jdk/jdk1.7.0_80/bin/java -verbose:gc -Xloggc:gc.l -XX:+PrintGCDetails -Djava.rmi.server.hostname=192.168.1.x -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=33306 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Xmx20m -Xmn10m -Xms20m -XX:PretenureSizeThreshold=100 -XX:+UseG1GC  PermGenTest

PS: G1下不建议设置Xmn。此处为了测试和CMS的对比,保留xmn参数。

image-20200506200156598

整个的GC过程,并没有对永久代进行回收。

但如果FullGC时,则会回收永久代

image-20200506200305600

所以也验证了前面所提到的永久代GC在不同的垃圾回收器下的不同表现。

参考

https://juejin.im/post/5df5fde36fb9a0162c486c71

https://www.cnblogs.com/paddix/p/5309550.html

https://cloud.tencent.com/developer/article/1415205

https://www.baeldung.com/java-permgen-metaspace

关于G1的Permgen说明:https://blogs.oracle.com/poonam/about-g1-garbage-collector%2c-permanent-generation-and-metaspace

https://segmentfault.com/a/1190000005036183

https://www.sczyh30.com/posts/Java/jvm-metaspace/

番外:

关于HotSpot和JRockit: https://www.zhihu.com/question/29265430/answer/43818804

关于JVM的问题,推荐到 https://blogs.oracle.com/ 查看,基本上是官方的人写的博客,可信度高。当然能力强的可以看JDK源码

TODO:

  1. CMS垃圾回收 https://www.cnblogs.com/littleLord/p/5380624.html
  2. 指针膨胀:https://my.oschina.net/u/2458458/blog/804654 https://gavinzhang1.gitbooks.io/java-jvm-us/content/diao_you_an_li_fen_xi_yu_shi_zhan.html
  3. Metaspace详解&坑:
    1. 关注点:
      1. 详解
      2. 内存碎片
      3. GC https://blogs.oracle.com/poonam/about-g1-garbage-collector%2c-permanent-generation-and-metaspace https://zouyx.github.io/posts/2019/10/01/Java-Metaspace%E4%BC%9A%E5%8F%91%E7%94%9FGC%E5%90%97.html
      4. 优点(No GC SCAN?)
    2. 链接:http://lovestblog.cn/blog/2016/10/29/metaspace/ https://atbug.com/java8-metaspace-size-issue/ https://www.sczyh30.com/posts/Java/jvm-metaspace/ http://java-latte.blogspot.com/2014/03/metaspace-in-java-8.html
  4. Metaspace碎片化问题:https://www.infoq.cn/article/Java-PERMGEN-Removed
  5. 推荐的几个JVM参数:https://www.ateam-oracle.com/recommended-jvm-parameters-for-11g-products
  6. 内存分布图:https://www.ateam-oracle.com/visualising-garbage-collection-in-the-jvm
  7. https://www.ateam-oracle.com/java-tuning-in-a-nutshell-part-1
  8. G1 https://zhuanlan.zhihu.com/p/54048685

关于JVM/JAVA相关的官方内容:https://www.ateam-oracle.com/ https://blogs.oracle.com/

https://www.linkedin.com/in/poonamparhar/

Hi Poonam Parhar

I found your blog from google, this is the link :https://blogs.oracle.com/poonam/about-g1-garbage-collector%2c-permanent-generation-and-metaspace

I have some questions to ask you , Can you do me a favor to explain these questions?

Questions:

  1. Is PermGen belongs to Java Heap? When JVM started ,it will allocate memory from OS, this memory contians heap and permgen memory ? And is the heap memory address adjacent to permgen memory address?
  2. Either OldGen or PermGen is full, permanent generation garbage collection will be triggered, Is that right? In Another word is "When triger permGen GC ?"
  3. what is MetaSpace's garbage collector? is G1? or CMS?
    1. Is metaspace gc not need gc scan and compacting?
    2. Is permgen gc more complex than metaspace gc ?

My English is not well ,If I don't make it clear , Please tell me .

Thank you very much ! I'm looking forward to your reply soon.

关于我及张二蛋又要扯蛋了

    一个不务正业的程序猿及这个程序猿写字的地方,这里可能有技术,有理财,有历史,有总结,有生活,偶尔也扯扯蛋,妥妥的杂货铺,喜欢可关注。
    酒已备好,等你来开
图片