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

news2024/10/1 17:25:46

作者简介:大家好,我是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进行分析:

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

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

四、总结

本章,我们通过一个程序示例,不断利用CGLIB生成动态代理类的方式,模拟了Metaspace区内存溢出的场景。下一章,我们将模拟Java虚拟机栈内存溢出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1395384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

15.云原生之k8s容灾与恢复实战

云原生专栏大纲 文章目录 Velero与etcd介绍Velero与etcd备份应用场景Velero与etcd在k8s备份上的区别 Velero备份恢复流程备份工作流程Velero备份时,若k8s集群发送变化,会发发生情况?Velero 备份pv,pv中数据变化,会发发…

C++ //练习 1.25 借助网站上的Sales_item.h头文件,编译并运行本节给出的书店程序。

C Primer(第5版) 练习 1.25 练习 1.25 借助网站上的Sales_item.h头文件,编译并运行本节给出的书店程序。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块 /********************************…

Flutter中使用minio_new库

前言 在移动开发中,我们常常会遇到需要在App中处理文件上传和下载的需求。Minio是一个开源的对象存储服务,它兼容Amazon S3云存储服务接口,可以用于存储大规模非结构化的数据。 开始之前 在pubspec.yaml文件中添加minio_new库的依赖&#xf…

最终Docker6:nacos集群部署

目录 mysql容器构建 1.进入soft 文件夹,创建mysql文件夹 2.进入conf文件夹 放入my.conf 配置文件 3.运行mysql容器 4.进入script文件夹 导入 sql文件 5.进入mysql 容器 并登录 6.创建nacos 数据库并使用,运行nacos.sql文件 7.授予用户所有权限 部…

loading stable diffusion model: FileNotFoundError解决方案

大家好,我是水滴~~ 本文主要介绍在安装 stable-diffusion-webui 时出现的 loading stable diffusion model: FileNotFoundError 问题的解决方案,希望能对你有所帮助。 文章目录 问题描述解决方案 问题描述 在安装 stable-diffusion-webui 过程中出现 l…

Linux环境下,针对QT软件工程搭建C++Test单元测试环境的操作指南

文章目录 前言一、安装QT二、安装CTest三、使用QT生成.bdf文件四、创建CTest工程注意事项 前言 CTest是Parasoft公司出品的一款可以针对C/C源代码进行静态分析、单元测试、集成测试的测试工具。本文主要讲解如何在Linux环境下,搭建QT插件版的CTest测试环境。 一、…

大数据开发之Hadoop(优化新特征)

第 1 章:HDFS-故障排除 注意:采用三台服务器即可,恢复到Yarn开始的服务器快照。 1.1 集群安全模块 1、安全模式:文件系统只接收读数据请求,而不接收删除、修改等变更请求 2、进入安全模式场景 1)NameNod…

GPT应用开发:GPT插件开发指南

欢迎阅读本系列文章!我将带你一起探索如何利用OpenAI API开发GPT应用。无论你是编程新手还是资深开发者,都能在这里获得灵感和收获。 本文,我们将继续展示聊天API中插件的使用方法,让你能够轻松驾驭这个强大的工具。 插件运行效…

记一次 .NET某道闸收费系统 内存溢出分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序几天内存就要爆一次,不知道咋回事,找不出原因,让我帮忙看一下,这种问题分析dump是最简单粗暴了,拿到dump后接下来就是一顿分析。 二&…

移动web开发流式布局

1.0 移动端基础 1.1 浏览器现状 PC端常见浏览器:360浏览器、谷歌浏览器、火狐浏览器、QQ浏览器、百度浏览器、搜狗浏览器、IE浏览器。 内核: 浏览器内核备注Safariwebkitwebkit内核是苹果公司开发的一款渲染引擎,目前已被很多手机厂商所采…

Java开发的审批流系统,前端使用vue,支持常态化工作审批流程

一、项目形式 springbootvueactiviti集成了activiti在线编辑器,快速开发平台,可插拔工作流服务。 二、项目介绍 本项目拥有用户管理,部门管理,代码生成,系统监管,报表,大屏展示,业…

文心一言使用分享

ChatGPT 和文心一言哪个更好用? 一个直接可以用,一个还需要借助一些工具,还有可能账号会消失…… 没有可比性。 通用大模型用于特定功能的时候需要一些引导技巧。 import math import time def calculate_coordinate(c, d, e, f, g, h,…

一套可以替代人工的Cnc机床自动上下料机器人

Cnc机床自动上下料|整体解决方案 CNC机床自动上下料是指通过自动化设备和系统,实现CNC机床在加工过程中自动进行上下料操作。这种自动化系统通常包括自动送料机和卸料机,可以根据加工工件的尺寸和形状自动调整上下料的位置和角度,从而提高生产…

SpringCloud整合Zookeeper代替Eureka案例

文章目录 本期代码下载地址zookeeper简介zookeeper下载安装新建服务提供者测试 新建消费者测试 本期代码下载地址 地址:https://github.com/13thm/study_springcloud/tree/main/days4 zookeeper简介 zookeeper是一个分布式协调工具,可以实现注册中心功能 关闭Lin…

VMware Workstation Pro虚拟机搭建

下载链接:Download VMware Workstation Pro 点击上方下载,安装过程很简单,我再图片里面说明 等待安装中。。。。。是不是再考虑怎样激活,我都给你想好了,在下面这个链接,点赞收藏拿走不谢。 https://downl…

DBA技术栈MongoDB:简介

1.1 什么是MongoDB? MongoDB是一个可扩展、开源、表结构自由、用C语言编写且面向文档的数据库,旨在为Web应用程序提供高性能、高可用性且易扩展的数据存储解决方案。 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当…

ElasticSearch的常用增删改查DSL和代码

es增删改查常用语法 我们日常开发中,操作数据库写sql倒是不可能忘记,但是操作es的dsl语句有时候很容易忘记,特地记录一下方便查找。 DSL语句 1、创建索引 -- 创建索引 PUT /my_index {"mappings": {"properties": {&…

数据结构:链式栈

stack.h /* * 文件名称&#xff1a;stack.h * 创 建 者&#xff1a;cxy * 创建日期&#xff1a;2024年01月18日 * 描 述&#xff1a; */ #ifndef _STACK_H #define _STACK_H#include <stdio.h> #include <stdlib.h>typedef struct stack{int data…

查找局域网树莓派raspberry的mac地址和ip

依赖python库&#xff1a; pip install socket pip install scapy运行代码&#xff1a; import socket from scapy.layers.l2 import ARP, Ether, srpdef get_hostname(ip_address):try:return socket.gethostbyaddr(ip_address)[0]except socket.herror:# 未能解析主机名ret…

Leetcode2207. 字符串中最多数目的子字符串

Every day a Leetcode 题目来源&#xff1a;2207. 字符串中最多数目的子字符串 解法1&#xff1a;贪心 一次遍历 设 pattern 的第一个字符为 x&#xff0c;第二个字符为 y。 根据题意&#xff0c;x 插入的位置越靠左&#xff0c;答案的个数越多&#xff1b;y 插入的位置越…