JVM实战(28)——模拟Metaspace内存溢出

这篇具有很好参考价值的文章主要介绍了JVM实战(28)——模拟Metaspace内存溢出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

本章,我们将通过示例代码演示Metaspace区域是如何发生内存溢出的,并根据内存快照进行分析。

我们回顾下Metaspace区发生内存溢出的一个场景:程序不停的动态生成类,然后不停的加载类到Metaspace区域,而且这些动态生成的类必须得是不能被回收的,一旦Metaspace区满了,就会触发Full GC,而由于Metaspace区中的对象无法被回收,此时就触发了Metaspace内存溢出。

二、示例程序

我们的示例程序采用CGLIB来动态生成类。

2.1 程序源码

    public class Demo1 {
        public static void main(String[] args) {
            long count = 0L;
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Car.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        if (method.getName().equals("run")) {
                            System.out.println("Before run, security checking...");
                            return methodProxy.invokeSuper(o, objects);
                        } else {
                            return methodProxy.invokeSuper(o, objects);
                        }
                    }
                });
    
                Car car = (Car) enhancer.create();
                car.run();
    
                System.out.println("Created " + ++count +" Car.");
            }
        }
    
        static class Car {
            public void run() {
                System.out.println("Car is running...");
            }
        }
    
        static class SafeCar extends Car{
            @Override
            public void run() {
                System.out.println("Car is running...");
                super.run();
            }
        }
    }

上述代码,通过CGLIB的Enhancer生成了一个Car的代理子类:

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Car.class);
    enhancer.setUseCache(false);

既然是Car的子类,就会有Car的所有方法,然后我们在调用子类的run方法时做了点手脚:

    enhancer.setCallback(new MethodInterceptor() {
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if (method.getName().equals("run")) {
                System.out.println("Before run, security checking...");
                return methodProxy.invokeSuper(o, objects);
            } else {
                return methodProxy.invokeSuper(o, objects);
            }
        }
    });

上述这段代码的意思是:如果调用了Car子类对象的方法,会先被这里的MethodInterceptor拦截,拦截后判断如果是run方法,则先做一些额外的工作——汽车安全检查,最后再执行父类的run方法。效果等同于:

    static class SubCar extends Car {
        @Override
        public void run() {
            System.out.println("Before run, security checking...");
            super.run();
        }
    }

2.2 JVM参数

接着,我们需要通过JVM参数限制下Metaspace区域的大小,我们把它设置为10MB,然后开启内存溢出时自动dump内存快照:
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

使用该参数执行程序,可以看到如下所示的打印输出,当创建到第258辆Car时,Metaspace区的内存被耗尽了,导致java.lang.OutOfMemoryError: Metaspace

    Created 258 Car.
    java.lang.OutOfMemoryError: Metaspace
    Dumping heap to ./\java_pid11836.hprof ...
    Heap dump file created [3470456 bytes in 0.120 secs]
    Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
        at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)
        at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
        at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)
        at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
        at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
        at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
        at com.ressmix.jvm.Demo1.main(Demo1.java:27)
    Caused by: java.lang.reflect.InvocationTargetException
        at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
        at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
        ... 6 more
    Caused by: java.lang.OutOfMemoryError: Metaspace
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        ... 11 more

三、问题分析

上述程序执行完后,会在程序根目录生成两个文件:gc.logjava_pid11836.hprof,gc.log是JVM运行时信息,java_pid11836.hprof就是内存快照。

3.1 GC日志分析

我们先来分析下gc.log:

    1.218: [GC (Allocation Failure) 1.276: [ParNew: 52480K->2051K(59008K), 0.0160380 secs] 52480K->2051K(190080K), 0.0745051 secs] [Times: user=0.05 sys=0.00, real=0.08 secs] 
    1.631: [GC (Allocation Failure) 1.631: [ParNew: 54531K->2855K(59008K), 0.0025661 secs] 54531K->2855K(190080K), 0.0026505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    1.881: [Full GC (Metadata GC Threshold) 1.881: [CMS: 0K->2708K(131072K), 0.0464583 secs] 36239K->2708K(190080K), [Metaspace: 9885K->9885K(1058816K)], 0.0467198 secs] [Times: user=0.05 sys=0.01, real=0.05 secs] 
    1.927: [Full GC (Last ditch collection) 1.927: [CMS: 2708K->1749K(131072K), 0.0104116 secs] 2708K->1749K(190144K), [Metaspace: 9885K->9885K(1058816K)], 0.0104936 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
    2.012: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1749K(131072K)] 1749K(190144K), 0.0001810 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2.012: [CMS-concurrent-mark-start]
    2.022: [CMS-concurrent-mark: 0.011/0.011 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
    2.057: [CMS-concurrent-preclean-start]
    2.058: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap
     par new generation   total 59072K, used 1363K [0x0000000701a00000, 0x0000000705a10000, 0x00000007166c0000)
      eden space 52544K,   2% used [0x0000000701a00000, 0x0000000701b54c68, 0x0000000704d50000)
      from space 6528K,   0% used [0x0000000704d50000, 0x0000000704d50000, 0x00000007053b0000)
      to   space 6528K,   0% used [0x00000007053b0000, 0x00000007053b0000, 0x0000000705a10000)
     concurrent mark-sweep generation total 131072K, used 1749K [0x00000007166c0000, 0x000000071e6c0000, 0x00000007c0000000)
     Metaspace       used 9912K, capacity 10090K, committed 10240K, reserved 1058816K
      class space    used 890K, capacity 913K, committed 1024K, reserved 1048576K
    2.063: [GC (CMS Final Remark) [YG occupancy: 1363 K (59072 K)]2.063: [Rescan (parallel) , 0.0002642 secs]2.063: [weak refs processing, 0.0000124 secs]2.063: [class unloading, 0.0012829 secs]2.065: [scrub symbol table, 0.0005776 secs]2.065: [scrub string table, 0.0001698 secs][1 CMS-remark: 1749K(131072K)] 3112K(190144K), 0.0024292 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

先触发了两次Young GC,主要是因为不断创建Car对象,最终Eden区无法容纳。我们关键看下Full GC,第一次Full GC如下:

    1.881: [Full GC (Metadata GC Threshold) 1.881: [CMS: 0K->2708K(131072K), 0.0464583 secs] 36239K->2708K(190080K), [Metaspace: 9885K->9885K(1058816K)], 0.0467198 secs] [Times: user=0.05 sys=0.01, real=0.05 secs]

Metadata GC Threshold告诉我们是因为Metasapce区空间不足而引起Full GC。可以看到,Metasapce区的对象已经快占满了10MB了——[Metaspace: 9885K->9885K(1058816K)],经过这次Full GC,里面的对象并没有被回收掉,接着就进行下一次Full GC,这是最后的拯救机会(Last ditch collection):

    1.927: [Full GC (Last ditch collection) 1.927: [CMS: 2708K->1749K(131072K), 0.0104116 secs] 2708K->1749K(190144K), [Metaspace: 9885K->9885K(1058816K)], 0.0104936 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

结果还是一样,Metaspace区中的对象依旧无法回收,也不够容纳新创建的类对象,所以JVM直接终止运行,并打印出最后的堆内存的情况。

3.2 内存快照分析

我们通过MAT工具来对内存快照java_pid11836.hprof进行分析:

JVM实战(28)——模拟Metaspace内存溢出,jvm专题,jvm

可以看到,大量的AppClassLoader占用了内存,点击Details继续看,发现里面有一大堆Car$$EnhancerByCGLIB对象,正是因为CGLIB动态生成的这些类导致了Metaspace被占满:

JVM实战(28)——模拟Metaspace内存溢出,jvm专题,jvm

于是我们排查代码,看到底是哪里不断的动态创建类对象,发现Enhancer对象没有做缓存,所以只要加上缓存,不要无限制去生成类就可以了。

四、总结

本章,我们通过一个程序示例,不断利用CGLIB生成动态代理类的方式,模拟了Metaspace区内存溢出的场景。下一章,我们将模拟Java虚拟机栈内存溢出。文章来源地址https://www.toymoban.com/news/detail-800823.html

到了这里,关于JVM实战(28)——模拟Metaspace内存溢出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM哪些区域会出现内存溢出

    JVM(Java Virtual Machine)是指Java虚拟机,是一种可以在不同平台上运行Java字节码的虚拟计算机。JVM是Java语言的核心,通过将Java代码编译成字节码并在JVM上运行,实现了跨平台的特性。 1.方法区(Metaspace) 方法区用于存储类的信息、静态变量和常量等数据。在JDK8及以上版本中

    2024年02月08日
    浏览(47)
  • JVM笔记 —— 出现内存溢出错误时时如何排查

    内存溢出错误分为StackOverflowError和OutOfMemoryError,前者是栈中出现溢出,后者一般是堆或方法区出现溢出,简称OOM 1. 栈溢出 StackOverflowError 栈溢出一般都是因为没有正确的结束递归导致的,无限递归导致超出栈内存(-Xss)限制时就会抛出StackOverflowError。这种情况直接根据异常

    2024年02月13日
    浏览(41)
  • jvm 程序计算器 程序计数器是否溢出 程序计数器是做什么的 java程序计数器会内存溢出吗 程序计数器作用与用处 jvm内存模型 jvm合集(一)

    1. jvm内存模型:     内存模型:                     程序计数器                     堆                     栈                     本地方法栈                     方法区 2. java代码编译为class文件,由类加载器加载到jvm,然后

    2024年02月09日
    浏览(48)
  • jvm内存溢出排查(使用idea自带的内存泄漏分析工具)

    想分析堆内存溢出,一定在运行jar包时就写上参数 -XX:+HeapDumpOnOutOfMemoryError ,可以看我之前关于如何运行jar包的文章。若你没有写。可以写上参数,重启你的项目,等你的项目发生下一次堆内存溢出异常,在运行的同级文件夹,将产生类似这样一个文件 java_pid74935.hprof ,若你

    2024年02月09日
    浏览(58)
  • Flink批处理metaspace内存溢出问题

    早上过来发现定时任务出现告警,Flink Jobs运行失败,登录Flinkweb后台一看,所有jobs都没了,slot也为0。 查看Flink日志,有以下错误异常: 根据错误异常不难得出,是因为metaspace内存溢出导致的。 通过日志能观察到是一个批处理任务(FlinkJobCheatFind)导致;这个批处理任务是通过

    2024年02月08日
    浏览(47)
  • 深入理解 JVM 之——Java 内存区域与溢出异常

    更好的阅读体验 huge{color{red}{更好的阅读体验}} 更好的阅读体验 本篇为深入理解 Java 虚拟机第二章内容,推荐在学习前先掌握基础的 Linux 操作、编译原理、计算机组成原理等计算机基础以及扎实的 C/C++ 功底。 该系列的 GitHub 仓库:https://github.com/Doge2077/learn-jvm Java 虚拟机在

    2024年02月09日
    浏览(64)
  • JVM-内存溢出的原因、CPU占满的原因

    OOM的排查思路_oom排查_java排坑日记的博客-CSDN博客 每个进程的内存(限制,譬如2G)=最大堆容量+最大方法区容量+程序计数器+虚拟机栈和本地方法栈。多线程下每个线程栈越大,越容易OOM.                 1)大对象(从数据库里一次请求了大量的数据)         

    2024年02月10日
    浏览(46)
  • JVM:全面理解线上服务器内存溢出(OOM)问题处理方案(一)

    前段时间生产上遇到了OOM问题,导致服务出现了短时间的不可用,还好处理及时,否则也将酿成大祸。OOM问题也是生产中比较重要的问题,所以本期我们针对OOM问题特别讲解,结合理论与实际案例来带大家彻底攻克OOM问题处理。 要解决问题,我们首先要清楚问题产生的原因。

    2024年02月12日
    浏览(44)
  • java面经03-虚拟机篇-jvm内存结构&垃圾回收、内存溢出&类加载、引用&悲观锁&HashTable、引用&finalize

    要求 掌握 JVM 内存结构划分 尤其要知道方法区、永久代、元空间的关系 结合一段 java 代码的执行理解内存划分 执行 javac 命令编译源代码为字节码 执行 java 命令 创建 JVM,调用类加载子系统加载 class,将类的信息存入 方法区 创建 main 线程,使用的内存区域是 JVM 虚拟机栈 ,

    2024年02月09日
    浏览(57)
  • JVM实战(23)——内存碎片优化

    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖,挖的越深,基础越扎实! 阶段1、深入多线程 阶段2、深入多线程设计模式 阶段3、深入juc源码解析

    2024年01月18日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包