记一次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

相关文章

python使用库爬取m3u8文件的示例

《python使用库爬取m3u8文件的示例》本文主要介绍了python使用库爬取m3u8文件的示例,可以使用requests、m3u8、ffmpeg等库,实现获取、解析、下载视频片段并合并等步骤,具有... 目录一、准备工作二、获取m3u8文件内容三、解析m3u8文件四、下载视频片段五、合并视频片段六、错误

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx

在Windows上使用qemu安装ubuntu24.04服务器的详细指南

《在Windows上使用qemu安装ubuntu24.04服务器的详细指南》本文介绍了在Windows上使用QEMU安装Ubuntu24.04的全流程:安装QEMU、准备ISO镜像、创建虚拟磁盘、配置... 目录1. 安装QEMU环境2. 准备Ubuntu 24.04镜像3. 启动QEMU安装Ubuntu4

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM