Java内存泄露生产环境排查过程,通透了
昨天线上环境崩了
java堆内存溢出。。。
报错:java.lang.OutOfMemoryError: Java heap space
下面我将我排查问题的思路和过程记录了下来
1. 场景
- 客户端跟Java服务端通过websocket连接建立长链接并发送语音数据(text格式)
- Java服务端跟听写引擎建立长链接并发送语音数据
- 听写引擎将实时转写的内容返回给Java服务端,Java服务端再将数据处理返回给客户端
2. 排查过程
2.1 通过命令查看异常进程
由于是堆内存溢出异常,所以我先检查了一下服务器的负载和堆内存试用情况
通过 top 命令查看服务器进程情况,发现pid为1的一个java进程cpu使用率达到90%多
这个进程正好是出现异常的这个java应用
通过 jps 命令也可以查看正在运行的java应用
2.2 分析堆内存使用情况
接下来查看这个应用的堆内存使用情况
通过 jmap -heap pid 这个命令
简要的对上图👆中的内存使用情况做简单的说明:
MinHeapFreeRatio = 40
说明:当堆内存的空闲比例低于这个值时,JVM 会尝试增加堆大小。
影响:设置为 40 表示当堆内存的空闲部分小于 40% 时,JVM 会考虑增加堆大小。
MaxHeapFreeRatio = 70
说明:当堆内存的空闲比例高于这个值时,JVM 会尝试减少堆大小。
影响:设置为 70 表示当堆内存的空闲部分大于 70% 时,JVM 会考虑减少堆大小。
MaxHeapSize = 536,870,912 (512.0 MB)
说明:JVM 最大堆内存大小。
影响:这是 JVM 可以使用的最大内存,当前设置为 512 MB。如果应用程序需要更多内存,可能会遇到 OutOfMemoryError。
NewSize = 11,141,120 (10.625 MB)
说明:新生代(Young Generation)的初始大小。
影响:这是新生代的最小容量,用于存储新创建的对象。
MaxNewSize = 178,913,280 (170.625 MB)
说明:新生代的最大容量。
影响:这是新生代可以增长到的最大大小。
OldSize = 22,413,312 (21.375 MB)
说明:老年代(Old Generation)的初始大小。
影响:这是老年代的最小容量,用于存储晋升对象。
NewRatio = 2
说明:老年代与新生代的比例。例如,NewRatio=2 意味着老年代是新生代的两倍大。
影响:调整新生代和老年代的比例,影响垃圾回收的频率和效率。
SurvivorRatio = 8
说明:Eden 区与 Survivor 区的比例。例如,SurvivorRatio=8 意味着 Eden 区是每个 Survivor 区的八倍大。
影响:调整 Eden 和 Survivor 区的大小,影响对象在年轻代中的存活时间。
MetaspaceSize = 21,807,104 (20.796875 MB)
说明:Metaspace 的初始大小,用于存储类元数据。
影响:Metaspace 的初始分配大小,影响类加载的速度。
CompressedClassSpaceSize = 1,073,741,824 (1024.0 MB)
说明:压缩类空间的大小,用于存储类的内部表示。
影响:限制类元数据的存储空间。
MaxMetaspaceSize = 17,592,186,044,415 MB
说明:Metaspace 的最大大小。实际上,这个值非常大,几乎等于无限制。
影响:理论上没有限制,但实际受限于物理内存和操作系统。
G1HeapRegionSize = 0 (0.0MB)
说明:G1 垃圾收集器的区域大小。这里显示为 0,意味着未使用 G1 收集器。
影响:确认当前使用的不是 G1 收集器。
简单分析:
可以很明显的看到新生代和老年代几乎完全被使用(99.99% 和 100%),这意味着几乎没有可用的堆内存
如果此时出现Java heap space很大可能是因为内存使用完 然后又有对象被分配,这个时候没有空间并且在执行垃圾回收的时候没有回收到对象 所以会溢出
可以通过 jstat -gc 命令查看垃圾回收信息(由于我当时没有执行 这里就不放截图了)
如果内存一直被占用没有被回收 大概率就是有对象或者数据一直被占用无法执行回收(这里是我们排查问题的关键)
2.3 借助工具分析
由于生产环境不方便排查问题,所以这里借助VisualVM工具
通过如下命令导出快照通过 VisualVM 进行分析
jmap -dump:format=b,file=/tmp/sdc.hprof pid
VisualVM 工具就不多介绍了,在jdk的bin目录中就可以启动
将我们从服务器上导出的 .hprof 文件导入到VisualVM中进行分析
导入 – > 选择文件
找到Object类页面
发现char[] 占用极高
点开发现里面存着大量的文本信息,这些信息正是客户端通过参数传过来的数据,这很不正常,一般来说客户端传递的参数不属于成员变量或者对象,数据会随着线程或者方法的执行完毕而销毁
感觉问题应该是出现在这里,我们在点开一条数据查看
ps:一般堆内存溢出这个异常都是由于对象重复创建或者数组存储数据太大导致的,而我这次查看没有发现有重复创建对象或者大对象,总之,一开始一头雾水
发现确实有对象在引用着他,所以无法被回收
2.4 本地复现问题
为了验证并且对比问题,我在本地复现了下生产环境的异常
为了快速模拟堆内存溢出,我将我本地程序堆内存故意调小,借助idea工具将堆内存调整为 60 M
第二是借助jmeter工具进行压测,由于我的接口是websocket接口,所以还需要借助一个插件
JMeterWebSocketSamplers-1.2.9.jar (大家可以在网上搜一下,如果不能下载可以找我获取, 公主号回复: jw)
将插件放到jmeter的安装目录 /lib/ext/ 下
jmeter的试用这里就不过多介绍了,不熟悉的可以自行查阅一下
我这里设置了 1 个线程,间隔 3 s,一直循环
通过我们的VisualVM进行监控
压测后观察服务和VisualVM还有压测工具
通过察看结果树发现 17 次之后就异常了
服务异常截图:
观察压测工具:
可以看到50Mb的时候就溢出了(这里大家可能要留意下,后面分析应该还会用到)
点击Heap Dump 生成快照文件
发现和生产环境是同样的问题
3. 问题解决过程
接下来就开始排查为啥前端传进来的数据会被一直占用
ps:一开始我为了省事使用http接口压测了一下传输大数据,眼看就把cpu干冒烟了 也没压出异常来 😭😭
那肯定就是websocket的锅 !!!
3.1 websocket缓冲区分析
上面查看分析快照的时候 也发现了被websocket的类HeapCharBuffer引用
那 HeapCharBuffer 中 messageBufferText 到底是干啥的呢?
messageBufferText 通常是指用于存储接收到的文本消息的缓冲区。当通过 WebSocket 接受到文本消息时,这些消息会暂时存储在这个缓冲区中,直到被应用程序读取。
说到 messageBufferText 那就不得不提到 maxTextMessageBufferSize
maxTextMessageBufferSize 定义了上述缓冲区的最大容量,即可以存储的最大文本消息量或大小。这个值设定了接收缓冲区的界限,防止由于过多未处理的消息导致内存耗尽
这里需要注意⚠️一点,也是引起我们问题的所在:
就是每次建立一个websocket连接都会创建一个最大的缓冲区 maxTextMessageBufferSize
那 maxTextMessageBufferSize 的大小是如何定义的呢,通过websocket的配置类来定义
我之前的定义是 512 k
@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();// 在此处设置bufferSizecontainer.setMaxTextMessageBufferSize(512000);container.setMaxBinaryMessageBufferSize(512000);container.setMaxSessionIdleTimeout(60 * 1000L); // 60s 超时时间return container;}
通过上面的分析,可以得出以下结论:
- 缓冲区设置的内存偏大导致建立大量连接的时候占用大量内存,而实际每次建立连接的时候缓冲区可能用不了512k
- 连接结束之后缓冲区没有被清空
还记得我们上面压测的数据吗,17次之后就异常了,按照我们的结论每次建立连接消耗512k内存,那17次就是8704k 大约8.5mb,我们的堆初始的使用内存大约为40mb多,然后出现溢出的时候使用内存是大约50mb,那正好就是加上我们的8.5mb出现的异常,这里只是一个估算,肯定还有其他地方有内存消耗
为了验证我又将 maxTextMessageBufferSize 的大小调整为了 20k
然后压测 发现300多次才出现异常,那肯定就是因为maxTextMessageBufferSize引起的
但是调整maxTextMessageBufferSize的大小还不能从根本上解决问题,还要找到问题的根源,那就是为啥连接关闭后缓冲区没有被回收。
3.2 GC Root 引用链分析
堆内存都满了,但是数据都没有被垃圾回收只能说明一个问题,就是你的对象还被别人引用着。
接下来我们就通过查看messageBufferText的引用链来寻找问题的根源
我直接截图吧,我们找到有问题的数据(这个在上面使用工具的时候已经讲过了),一开始我们没有往深了挖这个链路,现在通过GC Root这个工具将链路展开
可以很清晰的看到我们的messageBufferText 的大概引用流程:
messageBufferText --> ConcurrentHashMap --> ClassLoader(这个是根级)
messageBufferText一直在被ConcurrentHashMap引用着 无法被释放
最后我排查了下我的代码,连接每次都会正常关闭,问题就出在 ConcurrentHashMap 上,我使用 ConcurrentHashMap 存储了客户端实例,在存储的时候使用 seesionId 作为key,然后加了前缀。。
大概是这样:
但是在清除client实例的时候,大概是这样的(当时估计犯神经了):
神龙见首不见尾 !!!
虽然是由一个不起眼的小问题引起的,但是整个堆内存溢出的排查思路还是值得我们复盘和学习的。
One more thing
原来时间真的不是一条横跨在你面前的河,有着此岸和彼岸,而是一条挂在悬崖上的瀑布,奔流直下,一去无回。
相关文章:

Java内存泄露生产环境排查过程,通透了
昨天线上环境崩了 java堆内存溢出。。。 报错:java.lang.OutOfMemoryError: Java heap space 下面我将我排查问题的思路和过程记录了下来 1. 场景 客户端跟Java服务端通过websocket连接建立长链接并发送语音数据(text格式)Java服务端跟听…...

NHANES指标推荐:MDS
文章题目:The association between magnesium depletion score (MDS) and overactive bladder (OAB) among the U.S. population DOI:10.1186/s41043-025-00846-x 中文标题:美国人群镁耗竭评分 (MDS) 与膀胱过度活动症…...
以项目的方式学QT开发C++(二)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!
API 描述 函数原型 参数说明 push_back() 在 list 尾部 添加一个元素 void push_back(const T& value); value :要添 加到尾部的元 素 这个示例演示了如何创建 std::list 容器,并对其进行插入、删除和迭代操作。在实际应用中&am…...
Docker使用经验-从Image导出dockerfile并进行修改
好久没进行记录写作了,还是得进行下去 0 前言 项目上拉下来的docker-image在我自己电脑上创建的容器不能正常启动,创建者在容器里面添加的了用户,容器启动后会进入该用户 1 docker导出dockerfile dfimage是一个用于从Docker镜像生成Docker…...

【HTML5学习笔记1】html标签(上)
web标准(重点) w3c 构成:结构、表现、行为,结构样式行为相分离 结构:网页元素整理分类 html 表现:外观css 行为:交互 javascript html标签 1.html语法规范 1) 所有标签都在…...

计算机视觉---目标检测(Object Detecting)概览
一、目标检测定义与核心任务 1. 定义 任务:在图像/视频中定位并分类所有感兴趣目标,输出边界框(Bounding Box)和类别标签。核心输出: 坐标:((x_1, y_1, x_2, y_2))(左上角右下角)或…...

在vue3中使用Cesium的保姆教程
1. 软件下载与安装 1. node安装 Vue.js 的开发依赖于 Node.js 环境,因此我们首先需要安装 Node.js。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它允许你在服务器端运行 JavaScript 代码,同时也为前端开发提供了强大的工具支…...

IP地址、端口、TCP介绍、socket介绍、程序中socket管理
1、IP地址:IP 地址就是 标识网络中设备的一个地址,好比现实生活中的家庭地址。IP 地址的作用是 标识网络中唯一的一台设备的,也就是说通过IP地址能够找到网络中某台设备。 2、端口:代表不同的进程,如下图: 3、socket:…...
基于MCP的桥梁设计规范智能解析与校审系统构建实践
引言 在腾讯云开发者社区中,有多种MCP工具可以用于本系统的开发和优化中,以下是一些潜在的应用场景: PDF解析工具:如pdfplumber等,可以用于规范文件的预处理,提取文本和图像信息。自然语言处理工具…...

搭建运行若依微服务版本ruoyi-cloud最新教程
搭建运行若依微服务版本ruoyi-cloud 一、环境准备 JDK > 1.8MySQL > 5.7Maven > 3.0Node > 12Redis > 3 二、后端 2.1数据库准备 在navicat上创建数据库ry-seata、ry-config、ry-cloud运行SQL文件ry_20250425.sql、ry_config_20250224.sql、ry_seata_2021012…...
OM和SCADA的区别
在工业与能源领域,O&M(Operation and Maintenance,运维) 和 SCADA(Supervisory Control And Data Acquisition,监控与数据采集系统) 是两类截然不同的概念,前者是 人员与流程驱动…...
CentOS7 grub配置文件介绍
1. grub2-editenv list saved_entryCentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core) 这个命令查询出当前启动的内核版本。 2.grep ^menu /boot/grub2/grub.cfg menuentry CentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core) --class centos --class gnu-linux --class gnu --c…...
SpringBoot常用注解详解
文章目录 1. 前言2. 核心注解2.1 SpringBootApplication2.2 Configuration2.3 EnableAutoConfiguration2.4 ComponentScan2.5 Bean2.6 Autowired2.7 Qualifier2.8 Primary2.9 Value2.10 PropertySource2.11 ConfigurationProperties2.12 Profile 3. Web开发相关注解3.1 Control…...
Python字符串常用内置函数详解
文章目录 Python字符串常用内置函数详解一、基础字符串函数1. len() - 获取字符串长度2. ord() - 获取字符的Unicode码点3. chr() - 通过Unicode码点获取字符4. ascii() - 获取字符的ASCII表示 二、类型转换函数1. str() - 将对象转为字符串2. repr() - 获取对象的官方字符串表…...
MyBatis 批量新增与删除功能完整教程
一、功能概述 通过 MyBatis 动态 SQL 实现以下功能: 批量新增:一次性插入多条员工记录,支持自增主键回填。批量删除:根据 ID 数组一次性删除多条记录。二、代码逐行解析 1. Mapper 接口定义 // 批量新增:传入员工对象集合 void insertAll(List<Emp> empList);// …...
HTML常用标签用法全解析:构建语义化网页的核心指南
HTML作为网页开发的基石,其标签的合理使用直接影响页面的可读性、SEO效果及维护性。本文系统梳理HTML核心标签的用法,结合语义化设计原则与实战示例,助你构建规范、高效的网页结构。 一、基础结构与排版标签 1.1 文档结构 <!DOCTYPE htm…...
大数据架构选型全景指南:核心架构对比与实战案例 解析
目录 大数据架构选型全景指南:核心架构对比与实战案例解析1. 主流架构全景概览1.1 核心架构类型1.2 关键选型维度 2. 架构对比与选型矩阵2.1 主流架构对比表2.2 选型决策树 3. 案例分析与实现案例1:电商实时推荐系统(Lambda架构)案…...
FPGA: XILINX Kintex 7系列器件的架构
本文将详细介绍Kintex-7系列FPGA器件的架构。以下内容将涵盖Kintex-7的核心架构特性、主要组成部分以及关键技术,尽量全面且结构化,同时用简洁的语言确保清晰易懂。 Kintex-7系列FPGA架构概述 Kintex-7是Xilinx 7系列FPGA中的中高端产品线,基…...

RK3568-鸿蒙5.1与原生固件-扇区对比分析
编译生成的固件目录地址 ../openharmony/out/rk3568/packages/phone/images鸿蒙OS RK3568固件分析 通过查看提供的信息,分析RK3568开发板固件的各个组件及其用途: 主要固件组件 根据终端输出的文件列表,RK3568固件包含以下关键组件&#x…...

常见激活函数——作用、意义、特点及实现
文章目录 激活函数的意义常见激活函数及其特点1. Sigmoid(Logistic 函数、S型函数)2. Tanh(双曲正切函数)3. ReLU(Rectified Linear Unit修正线性单元)4. Softmax5. Swish(Google 提出ÿ…...
Spring模拟转账开发
完成转账代码的编写 service public class AccountServiceImpl implements AccountService {Autowiredprivate AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao accountDao;}public void pay(String out, String in, Double money)…...

基于微信小程序的在线聊天功能实现:WebSocket通信实战
基于微信小程序的在线聊天功能实现:WebSocket通信实战 摘要 本文将详细介绍如何使用微信小程序结合WebSocket协议开发一个实时在线聊天功能。通过完整的代码示例和分步解析,涵盖界面布局、WebSocket连接管理、消息交互逻辑及服务端实现,适合…...

小波变换+注意力机制成为nature收割机
小波变换作为一种新兴的信号分析工具,能够高效地提取信号的局部特征,为复杂数据的处理提供了有力支持。然而,它在捕捉数据中最为关键的部分时仍存在局限性。为了弥补这一不足,我们引入了注意力机制,借助其能够强化关注…...

【无标题】威灏光电哲讯科技MES项目启动会圆满举行
5月14日,威灏光电与哲讯科技MES项目启动会在威灏光电总部隆重举行。威灏光电董事长江轮、总经理刘明星、哲讯科技总经理崔新华、副总王子文及双方项目组成员共同出席,标志着两家企业在数字化领域的第二次深度合作正式启航。 强强联手,二度合作…...
腾讯云存储原理
我们来详细展开你提到的两个核心结构概念: 一、“基于分布式文件系统 对象存储技术” 是什么? 1. 分布式文件系统(DFS)基础 分布式文件系统是一种支持将数据分布在多个存储节点上、并对上层用户透明的文件系统。腾讯云COS虽然是…...

display:grid网格布局属性说明
网格父级 :display:grid(块级网格)/ inline-grid(行内网格) 注意:当设置网格布局,column、float、clear、vertical-align的属性是无效的。 HTML: <ul class"ls02 f18 mt50 sysmt30&…...

排序算法之高效排序:快速排序,归并排序,堆排序详解
排序算法之高效排序:快速排序、归并排序、堆排序详解 前言一、快速排序(Quick Sort)1.1 算法原理1.2 代码实现(Python)1.3 性能分析 二、归并排序(Merge Sort)2.1 算法原理2.2 代码实现…...

Java 并发编程归纳总结(可重入锁 | JMM | synchronized 实现原理)
1、锁的可重入 一个不可重入的锁,抢占该锁的方法递归调用自己,或者两个持有该锁的方法之间发生调用,都会发生死锁。以之前实现的显式独占锁为例,在递归调用时会发生死锁: public class MyLock implements Lock {/* 仅…...

基于对抗性后训练的快速文本到音频生成:stable-audio-open-small 模型论文速读
Fast Text-to-Audio Generation with Adversarial Post-Training 论文解析 一、引言与背景 文本到音频系统的局限性:当前文本到音频生成系统性能虽佳,但推理速度慢(需数秒至数分钟),限制了其在创意领域的应用。 研究…...
BUFDS_GTE2,IBUFDS,BUFG缓冲的区别
1、IBUFDS_GTE2 这是 Xilinx FPGA 中专门为 高速收发器(SerDes/GTX/GTH/GTY)参考时钟设计的差分输入缓冲器。 主要功能是将外部的差分时钟信号(如LVDS、LVPECL等)转换为FPGA内部的单端时钟信号,并保证信号的完整性和高…...