记一次TheadLocal使用方式不正确导致内存泄漏问题的排查和修复过程

本文主要是介绍记一次TheadLocal使用方式不正确导致内存泄漏问题的排查和修复过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、背景

  一个部门其他同事的上线了很久的项目近期频繁的内存溢出——几乎每天内存溢出一次,而且频率越来越高。在添加了进程守护之后,虽然可以在内存溢出后自动重启,但并没有解决内存溢出的问题。不甘其扰之后,决定仔细排查导致内存溢出的根本原因。

二、排查过程

  在将内存溢出的dump文件导出之后,通过Jprofiler进行分析,发现HashMap对象占用的内存很大,而且一直在增加。
  就在代码里面查询创建全局HashMap对象的地方,发现有一个地方使用了ThreadLocal,代码如下:

private static final ThreadLocal<Map<Class<?>,Unmarshaller>> uMapLocal = new ThreadLocal<Map<Class<?>,Unmarshaller>>(){@Overrideprotected Map<Class<?>, Unmarshaller> initialValue() {return new HashMap<>();}
};

  这是一个微信回调时会使用的Map,往这个Map里面put数据的代码如下:

public static <T> T convertToObject(Class<T> clazz,Reader reader){try {Map<Class<?>, Unmarshaller> uMap = uMapLocal.get();if(!uMapLocal.containsKey(clazz)){JAXBContext jaxbContext = JAXBContext.newInstance(clazz);Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();uMapLocal.put(clazz, unmarshaller);}return (T) uMapLocal.get(clazz).unmarshal(reader);} catch (JAXBException e) {e.printStackTrace();}return null;
}

  代码的本意是想避免对象的多次反序列化,想将已经反序列化过的对象放在一个全局的Map里面,下次如果这个Map中已经有了该对象就直接从Map里面获取,若没有则先将该对象反序列化之后置入这个Map中,再从该Map中获取。但他忽视了一点,这个HashMap是ThreadLocal的,微信在回调的时候每次都会新起一个线程,所以每次都会新创建一个HashMap对象。这就会导致内存泄漏。

三、修复

  将原来创建全局HashMap的地方的ThreadLocal去掉即可:

private static final Map<Class<?>,Unmarshaller> uMapLocal = new HashMap<>();

四、TheadLocal的使用

  ThreadLocal类型的变量从其命名上就可以知晓它是和线程有关的,每个线程各持有一份,并不是使用static修饰就变成了全局变量了。在使用的使用一定要注意clear。另外ThreadLocal类型的变量从一定意义上来说是可以用局部变量替换的,如果对ThreadLocal的原理不是很了解,最好不要使用,使用不当就可能导致内存泄漏问题。

五、排查过程中使用的工具和命令

  上面将问题排查、定位的过程极大的简化了。下面说一下具体的排查、定位和解决的详细过程。
  第一次该同事找我排查的时候,我将dump文件下载下来通过Jprofiler进行分析,显示是HashMap的内存占用很高,但HashMap对象并不是项目中定义的一个对象,并不好排查是哪个地方创建的HashMap。又再通过Jprofiler查看宕机时的线程的情况,定位到了出现问题的线程,然后查看代码,发现代码中有一个使用流的地方,但这个流在使用完之后没有关闭,就误以为是流未关闭导致的。在第一次修复时只是将这个流close掉了。
  后来该同事跟我说没有解决问题,还是每天内存溢出。于是就安装了arthas,在生产环境的服务器上使用arthas工具的dashboard观察,发现每次minorgc之后老年代的内存都会增加1到2M的内存,我就意识到应该是有地方内存泄露了。
请添加图片描述
  又查看当前堆内存中对象的创建情况:

jmap -histo:live 24353 >> /abc_class.log

  结果和使用Jprofiler查看的一致,HashMap对象的数量惊人的庞大:
请添加图片描述
  起初以为是项目中创建了一个全局的HashMap,然后不停的往该HashMap中put对象导致的。于是又从代码中找全局的HashMap,全部找出来之后没有发现存在一直向某个HashMap中put对象的问题。
  再看上面的截图中,发现是HashMap对象自身的实例个数庞大,而不是一个HashMap的内存庞大,也就是说有一个地方在一直创建HashMap实例,但这个HashMap肯定不是简简单单的局部变量,因为局部变量在栈调用结束之后是可以被回收的。
  最终终于找到了这个ThreadLocal的HashMap,解决了问题。

这篇关于记一次TheadLocal使用方式不正确导致内存泄漏问题的排查和修复过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/249460

相关文章

gradle第三方Jar包依赖统一管理方式

《gradle第三方Jar包依赖统一管理方式》:本文主要介绍gradle第三方Jar包依赖统一管理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景实现1.顶层模块build.gradle添加依赖管理插件2.顶层模块build.gradle添加所有管理依赖包

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

使用Python和Pyecharts创建交互式地图

《使用Python和Pyecharts创建交互式地图》在数据可视化领域,创建交互式地图是一种强大的方式,可以使受众能够以引人入胜且信息丰富的方式探索地理数据,下面我们看看如何使用Python和Pyec... 目录简介Pyecharts 简介创建上海地图代码说明运行结果总结简介在数据可视化领域,创建交互式地