项目频繁发生metaspace溢出,基于相关知识毫不犹豫的想到以下几点

  • 项目中使用过多反射
  • 项目中使用过多的动态代理技术
  • 项目中使用过多的lambda

项目启动完成后,框架层面很多东西固定了,运行期间最有可能引起问题的地方无疑是我们大量的使用了beanCopy那么检查项目发现各种方式的beanCopy都有,有apacheBeanUtils,有springBeanUtils,有cglibBeanCopy。并且我们的很多实体都有上百个字段。
于是构建相关案例如下:
先来对比下spring 中提供的两种beancopy的方式对metaspace的使用情况

package com.qimo.omsa.demo.metaspace;import java.util.concurrent.TimeUnit;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;/*** @Description TODO* @Author 姚仲杰#80998699* @Date 2022/3/18 13:45*/
public class MetaspaceTest {public static void main(String[] args) throws Exception {int i=1;
//        while(true) {//            TimeUnit.SECONDS.sleep(3);
//            reflect();
//            System.out.println(i);
//            i++;
//        }while(true) {TimeUnit.SECONDS.sleep(3);cglib();System.out.println(i);i++;}}public static void reflect() {Product product=new Product();ProductVO productVO = new ProductVO();BeanUtils.copyProperties(product,productVO);}public static void cglib(){Product product = new Product();ProductVO productVO = new ProductVO();BeanCopier beanCopier = BeanCopier.create(Product.class, ProductVO.class, false);beanCopier.copy(product,productVO,null);}}

第一种 BeanUtils.copyProperties,这种方式使用的是反射的方式进行调用我们使用jcmd 命令查看下metaspace的使用情况发现前15次调用情况如下jcmd 23328 VM.classloader_stats

Total = 3                                                     1276   7915520   7636416
ChunkSz: Total size of all allocated metaspace chunks
BlockSz: Total size of all allocated metaspace blocks (each chunk has several blocks)

而在15次之后的情况如下jcmd 23328 VM.classloader_stats

Total = 1035                                                  2321  14387200  10678952
ChunkSz: Total size of all allocated metaspace chunks
BlockSz: Total size of all allocated metaspace blocks (each chunk has several blocks)

可以看到在15次前classloader一直是3个,而15次之后增长到了1035个往后稳定在1035

先声明下我的product对象的属性是516个,每个属性各有两个方法 gettersetterproductVo也是同样的对象。结合数据我们不难看这1035-3/2刚好是516。也就是说每个方法使用了一个classloader。并且ChunkSzBlockSz 也是增加了非常多,这个增加意味着metaspace空间被使用了。
那么我们在同样的条件下测试下spring提供的另一个基于cglibBeanCopy结果如下
15次调用

Total = 3                                                      805   6180864   5358144
ChunkSz: Total size of all allocated metaspace chunks
BlockSz: Total size of all allocated metaspace blocks (each chunk has several blocks)

15次后调用

Total = 4                                                      820   6187008   5439976
ChunkSz: Total size of all allocated metaspace chunks
BlockSz: Total size of all allocated metaspace blocks (each chunk has several blocks)

我们发现cglibbeanCopy仅仅增加了一个classloader,并且对metaspace的使用增加幅度非常小。

意味着项目中如果大量使用了反射方式的beancopy就会创建大量的DelegatingClassLoader,那么这里为什么是15次之后才出现呢?我们顺着BeanUtils.copyProperties方法点进去找到代码如下:

public MethodAccessor newMethodAccessor(Method var1) {checkInitted();if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());} else {NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);var2.setParent(var3);return var3;}}

我们再来看下如else中的两个MethodAccessor实现
1、NativeMethodAccessorImpl
通过阅读以下代码我们可以知道inflationThreshold这个属性阈值的15,从命名我们可以得知这个实现是通过jni调用native的方式直接读取字节码。但是这里还有个setParent方法,等到inflationThreshold超过15时将字节码获取从jni方式转成DelegatingMethodAccessorImpl 方式。所以接着我们看DelegatingMethodAccessorImpl

class NativeMethodAccessorImpl extends MethodAccessorImpl {private final Method method;private DelegatingMethodAccessorImpl parent;private int numInvocations;NativeMethodAccessorImpl(Method var1) {this.method = var1;}public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());this.parent.setDelegate(var3);}return invoke0(this.method, var1, var2);}void setParent(DelegatingMethodAccessorImpl var1) {this.parent = var1;}private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

2、DelegatingMethodAccessorImpl
此方法并没有做什么,只是一个委托。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {private MethodAccessorImpl delegate;DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {this.setDelegate(var1);}public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {return this.delegate.invoke(var1, var2);}void setDelegate(MethodAccessorImpl var1) {this.delegate = var1;}
}

所以我们需要回看Native的实现超过15次之后的if语句中的代码

MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());this.parent.setDelegate(var3);

我们点开MethodAccessorGenerator发现它时通过字节码去构建了一个class并调用ClassDefiner.defineClass去加载了该class放到metaspace中。从而使得每个方法在调用15次之后从jni获取字节码的方式转到java层面直接加载来提升性能。我们发现在
newMethodAccessor这个方法里面还用到一个参数noInflation 默认为false也就是如果设置这个参数为true则直接走java层面的的字节码生成并加载。达到提升性能的效果。也就是如果设置这个值为true就不用关心inflationThreshold的值了这里为了提升性能也是煞费苦心了。
以上两个参数可以直接通过-Dsun.reflect.noInflation=true,-Dsun.reflect.inflationThreshold=15jvm传参的方式来设置。所以在使用反射调用method.Invoke()的时候,当你的使用次数超过15次就会为每一个method生成一个class。就会导致metaspace极速膨胀。因此这里这个参数也取名叫noInflation(膨胀)。而在cglib方式下则是以类为单位,所以同样也走到了相关的代码,但是它只生成了一个DelegatingClassLoader,所以它的metaspace内存占用比反射来的小很多。而在反射有缓存的情况下cglib方式还是比反射性能要高一点点。所以此处我们已经很明确这个问题出在项目中大量使用的反射的beancopy导致metaspace溢出,理论上来说这种情况可以通过调大metaspace得以解决,但是更好的解决方案是尽量使用cglib方式的beancopy

又一个beanCopy引发的血案metaspace溢出相关推荐

  1. 『转』度百死去飞秋一个BUG引发的血案

    作了一篇文章度百死去飞秋一个BUG引发的血案,昨天,度百死去的美国客户发邮件给我,说我的软件出问题了,我查来查去,发现居然是服务器上一个目录无法删除,一删除就报 cannot read from th ...

  2. 【Elasticsearch】es 一个数据精度引发的血案

    1.概述 在看博客的时候,发现有个博客 一个数据精度引发的血案 上面说,数据精度会丢失,然后我在 7.8.0版本的es测试,发现数据没有丢失. POST /index-lcc-0002/_doc {& ...

  3. ”一个馒头引发的血案“|记Mybatis之BindingException异常的产生及解决过程

    一. 业务场景 前几天壹哥带学生做一个项目,需要更新数据库中的车辆信息表,具体需求是要根据指定车辆的设备id(编号和设备ID均非主键)来更新车辆信息.壹哥要求学生们用Mybatis进行实现,所以就在对 ...

  4. 一个馒头引发的血案...请看完无极后观看此片,保笑死人不偿命

    一个馒头引发的血案... 采用搞笑的手法拍摄的,笑到你喷饭,极大的讽刺无极 下载地址:点击下载

  5. 摘要: Druid连接池一个设置引发的血案 -- 链接池出现问题

    摘要: Druid连接池一个设置引发的血案 今天在一台配置很低的机器上运行批量更新的程序~~~ 大概跑了三十分钟~~~这配置~~~这程序~~~ 然后华丽丽的报异常了~~~ 具体异常是这样的, DEBU ...

  6. 一个随机数引发的血案

    一个随机数引发的血案 我也来做一次标题党 原文地址:http://blog.csdn.net/WinsenJiansbomber/article/details/50604653 目录 一个随机数引发 ...

  7. 线上 CPU100% 异常案例:一个正则表达式引发的血案

    前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...

  8. 一个NODE_ENV 引发的血案

    1 表象 控制台报错 截图没有完整的截下来,其实右边行号并没有具体的行号, 显示为payment-809e8ff.js 1 很明显 这是 js语法错误 但是当点击开里面显示的是html内容 第一行的 ...

  9. 一个“alert” 引发的血案

    0 在还没有掌握全部证据之前就下结论会犯严重的错误,会使判断带有偏见.--<血字的研究> "齐识,路老板又来邮件了."白娜一脸无耐地说. "一定没好事吧?&q ...

最新文章

  1. win2008下的无线网卡设置
  2. jQuery-1.9.1源码分析系列(十) 事件系统——事件绑定
  3. [前台]---js中去掉双引号或者单引号
  4. FFA 2021 专场解读 - Flink 核心技术
  5. python -json文件的使用---
  6. nb信号和4g信号_【行业】万物互联的世界NB-IoT VS 4G,到底哪个更能打?
  7. layui数据表格增加自动换行后,拖动列宽固定列错乱变形
  8. 【一日一logo|day_8】坦格利安家族?修改什么的不存在的
  9. 体重 年龄 性别 身高 预测鞋码_用身高和体重数据进行性别分类的实验报告
  10. 监控mysql删除记录_监控删除记录
  11. pip 按照requirements.txt安装到对应的package名称的文件中
  12. Python爬虫-漫画柜漫画爬取
  13. MFC画带箭头的直线
  14. Intel有那些45纳米的CPU
  15. 《MATLAB SYNTAX》第1章 数据类型
  16. Unity3d--坦克对战游戏 AI 设计
  17. rndis模块 linux,openwrt 19.7 驱动添加rndis模式4G模块
  18. RegCloseKey
  19. 12306订票助手-给力火车票自动订票插件
  20. 【智慧社区解决方案】视频智能检测与分析技术如何赋能社区智慧化建设?

热门文章

  1. 关于内聚和耦合的理解
  2. 乔布斯语录:领袖和跟风者的区别在于创新
  3. 虚拟机提示更新服务器证书错误,vcenter的ssl证书报错,更新证书失败
  4. 荣耀盒子刷鸿蒙,荣耀盒子怎么样,网友给出3个不建议入手理由
  5. 【UE Niagara】实现简单的下雪、下雨天气效果
  6. 去除字符串的叠词:我我....我...我.要...要要...要学....学学..学.编..编编.编.程.程.程..程
  7. 瞧不上alert 老古董?使用alert实现一个精美的弹窗
  8. maven安装和配置阿里云镜像(各种详细配置)
  9. Js中String对象方法replace()用法详解
  10. 面向切面(动态代理)