当前位置: 首页 > news >正文

日志组件导致的内存溢出问题分析

1、 内存溢出日志

普通的http请求,导致堆内存直接溢出,看了下代码实现非常简单的一次DB查询且数据量也比较小,不可能导致内存溢出呢

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:649)
    at java.lang.StringBuffer.append(StringBuffer.java:387)
    at java.io.StringWriter.write(StringWriter.java:77)
    at java.io.StringWriter.append(StringWriter.java:202)
    at java.io.StringWriter.append(StringWriter.java:41)
    at com.google.gson.stream.JsonWriter.beforeValue(JsonWriter.java:645)
    at com.google.gson.stream.JsonWriter.value(JsonWriter.java:532)
    at com.google.gson.internal.bind.TypeAdapters$5.write(TypeAdapters.java:189)
    at com.google.gson.internal.bind.TypeAdapters$5.write(TypeAdapters.java:173)

2、分析代码实现

排除代码实现的问题那一定是在代码执行前干了什么时间导致内存溢出,查看日志发现异常发生在 ParamLogAspect.java 中,代码实现如下

@Before("execution(* com.example.controller..*(..))")
public void logBefore(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature)joinPoint.getSignature();String className = signature.getDeclaringType().getName();String methodName = signature.getName();Object[] args = joinPoint.getArgs();StringBuilder jsonArgsBuilder = new StringBuilder();try {jsonArgsBuilder.append(new Gson().toJson(arg));} catch (Throwable e1) {log.info("转换函数入参json失败 - {}", e1, args != null ? args.toString() : "");}log.info("[aop operation log] {}.{} , Params:{}", className, methodName, jsonArgsBuilder);
}

初看也没发现什么问题,断点发现卡在了 new Gson().toJson(arg) ,断点调试跟进发现入参类型为 HttpServletRequest ,难道它不可直接打印 ?

查看 HttpServletRequest 说明发现该类包含大量的内部状态和引用,比如输入流、会话信息、请求头等,直接序列化这些信息可能会形成循环引用或过于庞大,导致序列化过程占用过多内存甚至进入无限循环。为了避免这种情况,建议手动提取 HttpServletRequest 中的有用信息,并将这些信息序列化。

3、优化组件日志记录方式

将日志组件的实现改为参数部分初始化,再次请求就可以了

@Before("execution(* com.example.controller..*(..))")
public void logBefore(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature)joinPoint.getSignature();String className = signature.getDeclaringType().getName();String methodName = signature.getName();Object[] args = joinPoint.getArgs();StringBuilder jsonArgsBuilder = new StringBuilder();try {// 增加 HttpServletRequest 类型参数解析(HttpServletRequest 直接 toJson 会导致内存溢出)for (Object arg : args) {if(arg instanceof HttpServletRequest){HttpServletRequest request = (HttpServletRequest) args[0];Map<String, String> paramMap = new HashMap<>();Enumeration<String> parameterNames = request.getParameterNames();while (parameterNames.hasMoreElements()) {String paramName = parameterNames.nextElement();String paramValue = request.getParameter(paramName);paramMap.put(paramName, paramValue);}jsonArgsBuilder.append(new Gson().toJson(paramMap)).append(" ");}else {jsonArgsBuilder.append(new Gson().toJson(arg)).append(" ");}}} catch (Throwable e1) {log.info("转换函数入参json失败 - {}", e1, args != null ? args.toString() : "");}log.info("[aop operation log] {}.{} , Params:{}", className, methodName, jsonArgsBuilder);
}

4、深度分析

        什么原因导致的内存溢出呢,初步猜测可能导致的原因如下:

  • HttpServletRequest 对象内部存在循环引用,如:HttpSession、HttpServletRequest 和 ServletContext、ServletConfig 对象之间相互引用,导致 Gson 在处理这些循环引用时,可能会进入无限循环,导致内存溢出。
  • HttpServletRequest 内容及数量确实很大,占用大量内存,导致内存溢出
  • 非序列化字段或者动态生成的输入、输出流,在序列化过程中可能会引发异常或者占用大量内存。

        从异常日志并没有发现有循环引用的调用栈,难道真是太大了导致的溢出?导出dump文件接着看下,还真是太大导致的

一个 request 占用了 89.37 %的内存,确实离谱了 !!!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java 4.2 - MySQL
  • Swift 中的影像魔术:Core Video 的高级应用
  • Swift模块化:构建高效可维护代码的秘诀
  • ArcGIS空间自相关 (Global Moran‘s I)——探究人口空间格局的20年变迁
  • 数据恢复技术-手动修复MBR-/NTFS分区
  • Linux静态库和动态链接库的制作和使用
  • How to stream video in a loop via RTP using ffmpeg?
  • 打造redis缓存组件
  • CAN总线/CAN应用层协议设计,理解并实践仲裁段位域定义
  • vue vite创建项目步骤
  • 推荐系统实战(四)精排-交叉结构
  • 【第55课】XSS防御HttpOnlyCSP靶场工具等
  • 如何使用ssm实现游戏攻略网站的设计与实现+vue
  • 测试员阿聪的破局之路:从迷茫到帝都站稳脚跟,大佬亲授良方
  • 想学网络,为什么要先学数通?
  • 分享一款快速APP功能测试工具
  • co.js - 让异步代码同步化
  • EventListener原理
  • Java 网络编程(2):UDP 的使用
  • JavaScript实现分页效果
  • mockjs让前端开发独立于后端
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • storm drpc实例
  • Terraform入门 - 3. 变更基础设施
  • VUE es6技巧写法(持续更新中~~~)
  • WePY 在小程序性能调优上做出的探究
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 基于web的全景—— Pannellum小试
  • 前端知识点整理(待续)
  • 设计模式走一遍---观察者模式
  • 微信小程序--------语音识别(前端自己也能玩)
  • 追踪解析 FutureTask 源码
  • ionic异常记录
  • 交换综合实验一
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​io --- 处理流的核心工具​
  • # C++之functional库用法整理
  • #HarmonyOS:Web组件的使用
  • #laravel 通过手动安装依赖PHPExcel#
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (C++17) optional的使用
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (八)Flink Join 连接
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (一)Java算法:二分查找
  • .NET Framework .NET Core与 .NET 的区别
  • .net framework 4.0中如何 输出 form 的name属性。
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution
  • .NET Micro Framework初体验
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .NET性能优化(文摘)
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku