zoukankan      html  css  js  c++  java
  • Metaspace 之二--PermGen vs. Metaspace 运行时比较

    PermGen vs. Metaspace 运行时比较

      为了更好地理解Metaspace内存空间的运行时行为,

      将进行以下几种场景的测试:

    1. 使用JDK1.7运行Java程序,监控并耗尽默认设定的85MB大小的PermGen内存空间。

    2. 使用JDK1.8运行Java程序,监控新Metaspace内存空间的动态增长和垃圾回收过程。

    3. 使用JDK1.8运行Java程序,模拟耗尽通过“MaxMetaspaceSize”参数设定的128MB大小的Metaspace内存空间。

    首先建立了一个模拟PermGen OOM的代码

    public class ClassA {
        public void method(String name) {
        }
    }

    上面是一个简单的ClassA,把他编译成class字节码放到D:/classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class

    package java8;
    
    import java.io.File;
    import java.lang.management.ClassLoadingMXBean;
    import java.lang.management.ManagementFactory;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.List;
    
    public class OOMTest {
        public static void main(String[] args) {  
            try{  
                //准备url  
                URL url = new File("D:/classes").toURI().toURL();  
                URL[] urls = {url};  
                //获取有关类型加载的JMX接口  
                ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();  
                //用于缓存类加载器  
                List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();  
                while(true) {  
                    //加载类型并缓存类加载器实例  
                    ClassLoader classLoader = new URLClassLoader(urls);  
                    classLoaders.add(classLoader);  
                    classLoader.loadClass("ClassA");  
                    //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)  
                    System.out.println("total: "+ loadingBean.getTotalLoadedClassCount());  
                    System.out.println("active: "+ loadingBean.getLoadedClassCount());  
                    System.out.println("unloaded: "+ loadingBean.getUnloadedClassCount());  
                }  
            } catch(Exception e) {  
                e.printStackTrace();  
            }  
        }
    }

    虚拟机器参数设置如下:-verbose -verbose:gc

    设置-verbose参数是为了获取类型加载和卸载的信息

    设置-verbose:gc是为了获取垃圾收集的相关信息

    JDK 1.7 @64-bit – PermGen 耗尽测试

    Java1.7的PermGen默认空间为85 MB(或者可以通过-XX:MaxPermSize=XXXm指定)

    可以从上面的JVisualVM的截图看出:当加载超过6万个类之后,PermGen被耗尽。我们也能通过程序和GC的输出观察耗尽的过程。

    程序输出(摘取了部分)

    1. ......  
    2. [Loaded ClassA from file:/D:/classes/]  
    3. total: 64887 
    4. active: 64887 
    5. unloaded: 
    6. [GC 245041K->213978K(536768K), 0.0597188 secs]  
    7. [Full GC 213978K->211425K(644992K), 0.6456638 secs]  
    8. [GC 211425K->211425K(656448K), 0.0086696 secs]  
    9. [Full GC 211425K->211411K(731008K), 0.6924754 secs]  
    10. [GC 211411K->211411K(726528K), 0.0088992 secs]  
    11. ...............  
    12. java.lang.OutOfMemoryError: PermGen space 

    JDK 1.8 @64-bit – Metaspace大小动态调整测试

    Java的Metaspace空间:不受限制 (默认)

    从上面的截图可以看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增长到646MB,以满足程序中不断增长的类数据内存占用需求。我们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。但是,由于我们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。

    JDK 1.8 @64-bit – Metaspace 受限测试

    Java的Metaspace空间:128MB(-XX:MaxMetaspaceSize=128m)

    可以从上面的JVisualVM的截图看出:当加载超过2万个类之后,Metaspace被耗尽;与JDK1.7运行时非常相似。我们也能通过程序和GC的输出观察耗尽的过程。另一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能表明,如果可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。

    从Java程序的输出中看到如下异常。

    1. [Loaded ClassA from file:/D:/classes/]  
    2. total: 21393 
    3. active: 21393 
    4. unloaded: 
    5. [GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs]  
    6. [Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs]  
    7. java.lang.OutOfMemoryError: Metaspace 

    在设置了MaxMetaspaceSize的情况下,该空间的内存仍然会耗尽,进而引发“java.lang.OutOfMemoryError: Metadata space”错误。因为类加载器的泄漏仍然存在,而通常Java又不希望无限制地消耗本机内存,因此设置一个类似于MaxPermSize的限制看起来也是合理的。

    总结

    1. 之前不管是不是需要,JVM都会吃掉那块空间……如果设置得太小,JVM会死掉;如果设置得太大,这块内存就被JVM浪费了。理论上说,现在你完全可以不关注这个,因为JVM会在运行时自动调校为“合适的大小”;
    2. 提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;
    3. 隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。

    总结

    • Hotspot中的元数据现在存储到了元空间里。mmap中的内存块的生命周期与类加载器的一致。
    • 类指针压缩空间(Compressed class pointer space)目前仍然是固定大小的,但它的空间较大
    • 可以进行参数的调优,不过这不是必需的。
    • 未来可能会增加其它的优化及新特性。比如, 应用程序类数据共享;新生代GC优化,G1回收器进行类的回收;减少元数据的大小,以及JVM内部对象的内存占用量。
  • 相关阅读:
    Day 13 匿名函数 :内置函数: 闭包
    Day 12 生成器:生成器表达式:列表表达式:yield:yield from:内置函数
    最近这些天
    正则表达式 I
    Python 软件规范开发
    模块
    常用模块介绍 time datetime os sys hashlib json pickle collections
    模拟博客园登录 基础版
    SQL 必知必会
    Python中的线程和进程
  • 原文地址:https://www.cnblogs.com/duanxz/p/3613930.html
Copyright © 2011-2022 走看看