小心踩雷,一次Java内存泄漏排查实战

2023-11-06

问题出现

晚上七点多开始,我就开始不停地收到报警邮件,邮件显示探测的几个接口有超时情况。
多数执行栈都在:

java.io.BufferedReader.readLine(BufferReader.java:389)
java_io_BufferedReader$readLine.call(Unknown Source)
com.domain.detect.http.HttpClient.getResponse(HttpClient.groovy:122)
com.domain.detect.http.HttpClient.this$2$getResponse(HttpClient.groovy)

这个线程栈的报错我见得多了,我们设置的 HTTP DNS 超时是 1s,connect 超时是 2s,read 超时是 3s。
这种报错都是探测服务正常发送了 HTTP 请求,服务器也在收到请求正常处理后正常响应了,但数据包在网络层层转发中丢失了,所以请求线程的执行栈会停留在获取接口响应的地方。

这种情况的典型特征就是能在服务器上查找到对应的日志记录。而且日志会显示服务器响应完全正常。
与它相对的还有线程栈停留在 Socket connect 处的,这是在建连时就失败了,服务端完全无感知。
我注意到其中一个接口报错更频繁一些,这个接口需要上传一个 4M 的文件到服务器,然后经过一连串的业务逻辑处理,再返回 2M 的文本数据。
而其他的接口则是简单的业务逻辑,我猜测可能是需要上传下载的数据太多,所以超时导致丢包的概率也更大吧。
根据这个猜想,群登上服务器,使用请求的 request_id 在近期服务日志中搜索一下,果不其然,就是网络丢包问题导致的接口超时了。
当然这样 leader 是不会满意的,这个结论还得有人接锅才行。于是赶紧联系运维和网络组,向他们确认一下当时的网络状态。
网络组同学回复说是我们探测服务所在机房的交换机老旧,存在未知的转发瓶颈,正在优化,这让我更放心了,于是在部门群里简单交待一下,算是完成任务。

问题爆发

本以为这次值班就起这么一个小波浪,结果在晚上八点多,各种接口的报警邮件蜂拥而至,打得准备收拾东西过周日单休的我措手不及。
这次几乎所有的接口都在超时,而我们那个大量网络 I/O 的接口则是每次探测必超时,难道是整个机房故障了么?
我再次通过服务器和监控看到各个接口的指标都很正常,自己测试了下接口也完全 OK,既然不影响线上服务,我准备先通过探测服务的接口把探测任务停掉再慢慢排查。
结果给暂停探测任务的接口发请求好久也没有响应,这时候我才知道没这么简单。

解决问题

一、首先是 top free df 三连,结果还真发现了些异常:
在这里插入图片描述
我们的探测进程 CPU 占用率特别高,达到了 900%。
我们的 Java 进程,并不做大量 CPU 运算,正常情况下,CPU 应该在 100~200% 之间,出现这种 CPU 飙升的情况,要么走到了死循环,要么就是在做大量的 GC。

二、使用 jstat -gc pid [interval] 命令查看了 Java 进程的 GC 状态,果然,FULL GC 达到了每秒一次:
在这里插入图片描述
这么多的 FULL GC,应该是内存泄漏没跑了

三、使用 jstack pid > jstack.log 保存了线程栈的现场。使用 jmap -dump:format=b,file=heap.log pid 保存了堆现场
jstat 是一个非常强大的 JVM 监控工具,一般用法是:
jstat [-options] pid interval
它支持的查看项有:
class 查看类加载信息。
compile 编译统计信息。
gc 垃圾回收信息。
gcXXX 各区域 GC 的详细信息,如 -gcold。
使用它,对定位 JVM 的内存问题很有帮助。

排查问题

分析栈

栈的分析很简单,看一下线程数是不是过多,多数栈都在干嘛:

grep 'java.lang.Thread.State' jstack.log | wc -l
464

才四百多线程,并无异常:
grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n
10 at java.lang.Class.forName0(Native Method)
10 at java.lang.Object.wait(Native Method)
16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
344 at sun.misc.Unsafe.park(Native Method)

线程状态好像也无异常

分析堆文件

下载堆 dump 文件

堆文件都是一些二进制数据,在命令行查看非常麻烦,Java 为我们提供的工具都是可视化的,Linux 服务器上又没法查看,那么首先要把文件下载到本地。
由于我们设置的堆内存为 4G,所以 dump 出来的堆文件也很大,下载它确实非常费事,不过我们可以先对它进行一次压缩。
gzip 是个功能很强大的压缩命令,特别是我们可以设置 -1~-9 来指定它的压缩级别。
数据越大压缩比率越大,耗时也就越长,推荐使用 -6~7,-9 实在是太慢了,且收益不大,有这个压缩的时间,多出来的文件也下载好了。

使用 MAT 分析 jvm heap
MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类。
对于这次分析,果断选择 memory leak suspect:
在这里插入图片描述

从上面的饼图中可以看出,绝大多数堆内存都被同一个内存占用了,再查看堆内存详情,向上层追溯,很快就发现了罪魁祸首。

在这里插入图片描述

分析代码

找到内存泄漏的对象了,在项目里全局搜索对象名,它是一个 Bean 对象,然后定位到它的一个类型为 Map 的属性。
这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果,每次探测完都塞到 ArrayList 里去分析。
由于 Bean 对象不会被回收,这个属性又没有清除逻辑,所以在服务十来天没有上线重启的情况下,这个 Map 越来越大,直至将内存占满。
内存满了之后,无法再给 HTTP 响应结果分配内存了,所以一直卡在 readLine 那里。而我们那个大量 I/O 的接口报警次数特别多,估计跟响应太大需要更多内存有关。
给代码 owner 提了 PR,问题圆满解决。

小结

其实还是要反省一下自己的,一开始报警邮件里还有这样的线程栈:


groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:166)
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)

看到这种报错线程栈却没有细想,要知道 TCP 是能保证消息完整性的,况且消息没有接收完也不会把值赋给变量。
这种很明显的是内部错误,如果留意后细查是能提前查出问题所在的,查问题真是差了哪一环都不行啊。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

小心踩雷,一次Java内存泄漏排查实战 的相关文章

  • Maven 未运行 Spring Boot 测试

    我有一个要测试的 Spring Boot REST API 我可以在 Eclipse 中手动运行测试 无需 Maven 并通过将应用程序作为 JUnit 测试运行 它运行良好并显示结果 但是mvn test正如您将在下面发现的那样 它不起作
  • 如果计算的哈希码超过整数最大限制,会发生什么?

    这是 Java HashTable 类的 hashCode 实现 如果哈希表中的元素数量很大并且哈希码超过 INTEGER MAX LIMIT 2 147 483 648 到 2 147 483 647 该怎么办 我假设 hashCodes
  • firebase android 基于类的更新不尊重字段名称的大小写

    我声明了以下类 注意大小写选择 public class User private String DisplayName private Boolean Proxy false SuppressWarnings unused public
  • Java 密钥库 - 以编程方式从密钥库文件中选择要使用的证书

    我有一个 java 密钥库文件 其中包含多个客户端证书 我希望在 Java 应用程序中仅选择其中一个证书来连接到服务 有没有一种简单的方法可以做到这一点 到目前为止 我找到解决方案的唯一方法是使用原始密钥库文件中的客户端证书详细信息 通过其
  • Vaadin框架播放视频

    我可以使用 Vaadin Framewotk 播放视频吗 主要思想是从本地驱动器加载 flv 或 avi 格式的视频文件 并使用 vaadin 框架在网络上播放 谢谢 Sampler中有一个示例 http demo vaadin com s
  • 为什么需要添加工件 JSR305 才能使用 Guava 14+?

    在stackoverflow上查找信息时 我看到了一个与我类似的问题 但没有真正的答案here https stackoverflow com questions 3800033 guava r07 gwt and javax annota
  • 如何获取JavaFX的版本号?

    如何在运行时找出我正在使用哪个版本的 JavaFX 简单的方法之一就是简单地阅读javafx properties文件位于您的 JAVA HOME jre lib目录 我现在安装了 Java 1 7 u9 与之捆绑的 JavaFX 是 v2
  • 如何将 (A OR B) AND C 等表达式展开为 A AND C OR A AND B?

    我努力了Javaluator这帮助我评估了这样的表达式 A OR B AND C 但现在我只想扩展表达式 A OR B AND C to A AND C OR A AND B任何人都可以告诉我如何在 Java 任何 API 中执行此操作或任
  • JConsole主类

    我正在尝试使用其 Main 类从命令行启动 JConsole 我提取了 jconsole jar 的内容 在 MANIFEST MF 中我可以看到 Main Class sun tools jconsole JConsole 所以我尝试运行
  • ActiveMQ 桥连接器到 WebSphereMQ 而不使用 XML 配置

    我想在嵌入式代理中创建一个代理来代理 ActiveMQ 和 WebSphere MQ 之间的连接 我知道 activemq 中存在网络连接器来执行此操作 代理到代理 但我不知道如何配置它以连接到 WebSphere MQ 在网络上进行搜索时
  • 无法解析 ListView 适配器中的 getSystemService 方法

    我正在研究约翰霍顿的Android 编程初学者 目前正在尝试创建一个笔记应用程序 霍顿刚刚介绍ListViews 但是 我遇到了麻烦adapter class public class NoteAdapter extends BaseAda
  • 原型组件的 Spring 事件处理

    假设我有两个组件 X 和 Y 其中 X 是单例 而 Y 不是 当我发布XUpdateEvent时 没有问题 我可以捕获该事件 但是 对于 YUpdateEvent 我无法捕获事件 Spring 为每个触发的事件创建新实例 而不是使用已经创建
  • 如何在 TestNG 报告中包含 Log4j2 消息

    我希望在所有测试用例的 TestNG 报告中提供 Log4j2 日志记录信息 TestNG 使用一个名为 Reporter java 的特殊记录器类来跟踪日志输出并将其保存在其结果 XML 中 在 log4j 中 可以简单地创建一个路由到
  • 结果显示图像上有衬里

    我正在使用 opencv 和 android ndk 下面是我的 jni 代码 void Vignete Mat img1 Mat img2 Mat out resize img1 img1 img2 size img1 convertTo
  • ImageIO read() 和 write() 操作后 GIF 图像变得错误

    我有这个代码 它只是读取 GIF 文件 用背景重新绘制它 然后输出到新的 GIF 文件 问题是结果文件变得奇怪 我不知道为什么它的质量变得很差 JPG 文件不会出现此问题 如何修复它 import java awt Color import
  • 用于计算句子中单词数的正则表达式

    public static int getWordCount String sentence return sentence split a zA Z0 9 a zA Z0 9 1 length sentence replaceAll a
  • RecyclerView:禁用焦点变化引起的滚动

    TL DR我有一个RecyclerView of EditTexts 当用户注意力集中时EditText 1并点击EditText 2 我想EditText 2获得焦点 但我不想要ReyclerView滚动 我怎样才能实现这个目标 我正在尝
  • 如何指示 yum 安装特定版本的 OpenJDK

    我尝试安装openjdk in the redhat服务器 如何安装指定版本 我要安装的版本是 11 0 4 使用以下命令安装的版本是11 0 6 yum install java 11 openjdk devel 曾与 yum showd
  • 删除子类中的注释?

    我有一个子类 需要一个注释 在删除的父类中声明 做这个的最好方式是什么 public class Parent MyAnnoation String foobar public class Child extends Parent here
  • 所有语言中特殊字符的 Java 正则表达式

    在我的用户输入字段中 我想允许某些特殊字符 字母和数字的组合 我应该确保正则表达式模式在输入时允许此设置任何语言 基本上我构建的这个正则表达式也应该支持 unicode 表示 如何使用 Java 中的 Pattern 类来实现这一点 这里给

随机推荐

  • Android WebView加载本地统一HTML界面样式文件并填充内容

    前言 之前加载HTMl图文都是使用TextView 但是现在需要统一三个端的样式 给出了一个HTML文件 我想反正都是HTML格式的 TextView应该也没问题 我就将文本直接填充进去 一运行 发现Html fromHtml 无法解析 l
  • ARM芯片开发(S5PV210芯片)——SD卡启动

    1 SD卡启动 顾名思义就是启动代码存放在SD卡中 设备从SD卡中启动 用SD卡启动有一些好处 譬如可以在不借用专用烧录工具 类似Jlink 的情况下对SD卡进行刷机 然后刷机后的SD卡插入卡槽 SoC既可启动 譬如可以用SD卡启动进行量产
  • 边缘云计算简介

    去年底 中国电子技术标准化研究院 阿里云等单位共同编制并发布了一份 边缘云计算技术与标准化白皮书 定义了边缘云计算的概念和标准等 白皮书篇幅略长 边缘计算社区将通过几篇文章拆解白皮书 边缘计算概念 和云计算出现的时候一样 目前业界对边缘计算
  • UniswapV2核心合约学习(1)— UniswapV2Factory.sol

    记得朋友圈看到过一句话 如果Defi是以太坊的皇冠 那么Uniswap就是这顶皇冠中的明珠 Uniswap目前已经是V2版本 相对V1 它的功能更加全面优化 然而其合约源码却并不复杂 本文为个人学习UniswapV2源码的系列记录 一 Un
  • 笔记/mysql数据库备份与恢复

    MySQL mysqldump命令详解 1 数据库信息 数据库地址 127 0 0 1 数据库用户名 root 数据库密码 1234 数据库名称 test1 数据库名称 test2 数据库名称 test3 mysqldump目录 usr b
  • 高级I/O函数

    应用层程序能往一个TCP连接中写入多少字节的数据 取决于对方的接收窗口的大小和本端的拥塞窗口的大小 管道本身有一个容量限制 规定如果应用程序不将数据从管道读走的话 该管道最多能写入多少字节的数据 自Linux 2 6 11内核起 管道容量的
  • AI Studio 飞桨 零基础入门深度学习笔记2-基于Python编写完成房价预测任务的神经网络模型

    AI Studio 飞桨 零基础入门深度学习笔记2 基于Python编写完成房价预测任务的神经网络模型 波士顿房价预测任务 线性回归模型 线性回归模型的神经网络结构 构建波士顿房价预测任务的神经网络模型 1 数据处理 1 1 读入数据 1
  • 企业订单管理软件

    企业订单管理软件 网上订货宝APP系统功能介绍 一 系统概述和用途 系统基于网络 实现厂家和代理商批发商通过网络下单订货功能 二 解决问题 1 解决订单管理混乱问题 2 解决订单遗漏问题 3 解决订单欠款遗漏问题 4 解决不同客户不同价格管
  • 在Windows下使用Python嵌入式环境包

    在 Python Releases for Windows 页面下载你需要的那个版本的 Windows embeddable package 64 bit 文件 这样就得到一个 python x x x embed amd64 zip 文件
  • Hash算法的使用

    Hash算法的使用 标签 默认分类 发表时间 2011 08 06 06 35 作者 GliderX khsing 分享到 出处 http hi baidu com gliderx 在对语料文本进行2 3元切分时 需要借助hash表来获得切
  • 信息学奥赛一本通 1170:计算2的N次方

    题目链接 http ybt ssoier cn 8088 problem show php pid 1170 思路 计算 2 2 2 的 n n n 次方相当于大整数 1
  • 目标检测-yolov50-火灾识别可视化

    使用yolov5模型对利用开源火灾数据集进行训练模型 实现对照片 视频以及摄像头的火焰烟雾的检测 详细的环境配置见 目标检测 YOLOV5 口罩检测 学习目标 数据集的准备 预训练权重 训练模型 训练模型路径的修改 模型测试 pyqt可视化
  • 完整代码CNN-LSTM多变量时间序列预测

    import torch from torch import nn import pandas as pd import numpy as np import matplotlib pyplot as plt from sklearn pr
  • vue源码解析---AST抽象语法树

    目录 首先AST是什么 指针思想 递归深入 斐波那契数列 递归深入 形式转换 栈结构 AST语法树 首先AST是什么 在计算机科学中 抽象 语法树 abstract syntax tree或者缩写为AST 或者语法树 syntax tree
  • Finding a needle in Haystack: Facebook’s photo storage

    文章目录 Finding a needle in Haystack Facebook s photo storage 简介 背景 设计细节 Haystack Directory Haystack Cache Haystack Store 优
  • QWidget、QDialog及QMainWindow的区别与联系

    QWidget类是所有用户界面对象的基类 QMainWindow和QDialog都是QWidget的子类 一般来说 如果需要嵌入到其他窗体中 则基于QWidget创建 如果是顶级对话框 则基于QDialog创建 如果是主窗体 则基于QMai
  • H5如何直接跳转小程序?

    1 云开发方式 不推荐 不推荐理由 1 要钱 2 麻烦 需要兼容 参考链接 https developers weixin qq com miniprogram dev wxcloud guide staticstorage jump mi
  • 稀疏重构算法详解

    引入 在室内环境中 多径信号具有天然的空间稀疏性 根据压缩感知理论可知 如果信号是可压缩的或者在某个变换域是稀疏的 可以采用一个随机测量矩阵将高维信号映射到一个低维空间上 通过求解优化问题 以很高的概率重构出原始信号 因此 在该理论框架下
  • 带分数

    标题 带分数 100 可以表示为带分数的形式 100 3 69258 714 还可以表示为 100 82 3546 197 注意特征 带分数中 数字1 9分别出现且只出现一次 不包含0 类似这样的带分数 100 有 11 种表示法 题目要求
  • 小心踩雷,一次Java内存泄漏排查实战

    问题出现 晚上七点多开始 我就开始不停地收到报警邮件 邮件显示探测的几个接口有超时情况 多数执行栈都在 java io BufferedReader readLine BufferReader java 389 java io Buffer