近期遇到一个堆外内存导致swap飙高的问题,这类问题比较罕见,因此将整个排查过程记录下来了

现象描述

最近1周线上服务器时不时出现swap报警(swap超过内存10%时触发报警,内存是4G,因此swap超过400M会触发报警),每次都是童鞋们通过重启tomcat解决的;
但导致的根本原因是什么呢?必须找到根本原因才行,总是这么重启就有点low了

问题排查

于是找了1台占用了swap但还未触发报警的服务器进行了排查
以下是当时通过top命令观察到的结果

23:03:22 swap占用了354M的内存

23:55:42 swap占用了398M的内存

原因分析

到底是什么原因导致swap飙高呢?肯定是tomcat,因为每次重启tomcat就解决了;但根本原因是?

谁占用了swap

通过以下脚本 swap.sh

#!/bin/bash
# Get current swap usage for all running processes
# Erik Ljungstrom //
do_swap () {
SUM=
OVERALL=
for DIR in `find /proc/ -maxdepth -type d | egrep "^/proc/[0-9]"` ; do
PID=`echo $DIR | cut -d / -f `
PROGNAME=`ps -p $PID -o comm --no-headers`
for SWAP in `grep Swap $DIR/smaps >/dev/null| awk '{ print $2 }'`
do
let SUM=$SUM+$SWAP
done
echo "PID=$PID - Swap used: $SUM - ($PROGNAME )"
let OVERALL=$OVERALL+$SUM
SUM= done
echo "Overall swap used: $OVERALL"
}
do_swap |awk -F[\ \(] '{print $5,$1,$8}' | sort -n | tail -

可以看出PID=19911这个进程使用了324M的swap

通过grep进程号19911可以看出确实是tomcat占用swap最多

进程19911占用总的物理内存是3.1G,java占用的堆内内存大小为2.78G,剩下的320M是堆外内存占用的

Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss]

2779M=2048M+268M+463*1M

sudo -u tomcat ./jinfo -flag MaxPermSize
-XX:MaxPermSize= java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = {pd product}
intx ThreadStackSize = {pd product}
intx VMThreadStackSize = {pd product}
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) -Bit Server VM (build 24.45-b08, mixed mode) java -XX:+PrintFlagsFinal -version | grep -i permsize
uintx AdaptivePermSizeWeight = {product}
uintx MaxPermSize = {pd product}
uintx PermSize = {pd product}
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) -Bit Server VM (build 24.45-b08, mixed mode)

哪行代码占用了堆外内存

堆内内存溢出可以直接通过MAT分析堆信息就可以定位到具体的代码,但是对于堆外内存就必须通过BTrace来解决

google-perftools 定位类名和方法名

如何安装和使用google-perftools见这里
由于要启动google-perftools需要重启tomcat,所以重启tomcat后,PID从19911变成了9176

重启tomcat后,会自动生成heap文件,文件名的命名规范是gperf_pid.xxx.heap,所以我们只需要关注gperf_9176.*即可

[xxxx@xxxx   /home/xxx/logs]$ ll *.heap
…...
-rw-r--r-- tomcat tomcat May : gperf_9171..heap
-rw-r--r-- tomcat tomcat May : gperf_9173..heap
-rw-r--r-- tomcat tomcat May : gperf_9174..heap
-rw-r--r-- tomcat tomcat May : gperf_9175..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
-rw-r--r-- tomcat tomcat May : gperf_9176..heap
…...

分析heap文件

/home/google-perftools/bin/pprof  --text  /home/java  /home/logs/gperf_9176..heap
Using local file /home/java.
Using local file /home/logs/gperf_9176..heap.
Total: 186.4 MB
91.2 48.9% 48.9% 91.2 48.9% updatewindow
52.5 28.2% 77.1% 52.5 28.2% os::malloc
38.0 20.4% 97.4% 38.0 20.4% inflateInit2_
3.0 1.6% 99.0% 3.0 1.6% init
0.8 0.4% 99.5% 0.8 0.4% ObjectSynchronizer::omAlloc
0.4 0.2% 99.7% 0.4 0.2% readCEN
0.3 0.2% 99.9% 38.3 20.5% Java_java_util_zip_Inflater_init
0.1 0.1% 100.0% 0.1 0.1% _dl_allocate_tls
0.0 0.0% 100.0% 0.0 0.0% _dl_new_object
0.0 0.0% 100.0% 1.1 0.6% Thread::Thread
0.0 0.0% 100.0% 0.0 0.0% CollectedHeap::CollectedHeap
0.0 0.0% 100.0% 0.0 0.0% Events::init
0.0 0.0% 100.0% 0.4 0.2% ZIP_Put_In_Cache0
0.0 0.0% 100.0% 0.0 0.0% read_alias_file
0.0 0.0% 100.0% 0.0 0.0% _nl_intern_locale_data

可以看出是java.util.zip.Inflater的init()占用了比较多的内存

通过BTrace定位代码调用方

编写代码BtracerInflater.java对init方法进行拦截

import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*; import java.nio.ByteBuffer;
import java.lang.Thread; @BTrace public class BtracerInflater{
@OnMethod(
clazz="java.util.zip.Inflater",
method="/.*/"
)
public static void traceCacheBlock(){
println("Who call java.util.zip.Inflater's methods :");
jstack();
}
}

运行BTrace

[xxxx@l-xxx.xx.xx /home/xxx/btrace-bin/bin]$ sudo -u tomcat ./btrace -cp ../build  BtracerInflater.java|more
Who call java.util.zip.Inflater's methods :
java.util.zip.Inflater.<init>(Inflater.java:)
java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:)
java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:)
com.xxx.OrderDiffUtil.ungzip(OrderDiffUtil.java:)
com.xxx.OrderDiffUtil.parse(OrderDiffUtil.java:)
com.xxx.FaxOrderEventListener.takeSectionChangedInfo(FaxOrderEventListener.java:)
com.xxx.FaxOrderEventListener.onMessage(FaxOrderEventListener.java:)
.......

可以看出是OrderDiffUtil的ungzip()调用了java.util.zip.Inflater的init()

看看OrderDiffUtil.ungzip()

private static String ungzip(String encodeJson) {
ByteArrayOutputStream out = new ByteArrayOutputStream(encodeJson.length() * 5);
ByteArrayInputStream in = null;
try {
in = new ByteArrayInputStream(Base64.decode(encodeJson));
} catch (UnsupportedEncodingException e) {
return "{}";
}
try {
GZIPInputStream gunzip = new GZIPInputStream(in);
byte buffer[] = new byte[1024];
int len = 0;
while ((len = gunzip.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
return "{}";
}
try {
return out.toString("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
}
return "{}";
}

可见gunzip未被close

所以根本原因是未调用GZIPInputStream的close()关闭流导致堆外内存占用

【JVM】记录一次线上SWAP偏高告警的故障分析过程的更多相关文章

  1. 你要偷偷学会排查线上CPU飙高的问题,然后惊艳所有人!

    GitHub 20k Star 的Java工程师成神之路,不来了解一下吗! GitHub 20k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 20k Star 的Java工 ...

  2. Linux(2)---记录一次线上服务 CPU 100%的排查过程

    Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...

  3. 记录一次线上bug

    记录一次线上bug,总的来说就是弱网和重复点击.特殊值校验的问题. 测试场景一:        在3g网络或者使页面加载速度需要两秒左右的时候,输入学号,提交学生的缴费项目,提交完一个 学生的缴费后, ...

  4. 【jvm】来自于线上的fullGC分析

    系统最近老年代的内存上升的比较快,三到四天会发生一波fullGC.于是开始对GC的情况做一波分析. 线上老年代2.7G,年轻带1.3G老年代上升较快,3天一波fullGC,并且fullGC会把内存回收 ...

  5. 原创 记录一次线上Mysql慢查询问题排查过程

    背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...

  6. 利用JVM在线调试工具排查线上问题

    在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因.为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出 ...

  7. 记录一次线上实施snmp

    公司要实施一个部级的项目,我们公司的提供的产品要对接下客户的一个平台监控平台,该监控平台使用snmp,我们公司的产品不支持snmp,所以由我负责在现网实施snmp,记录这次现网 一.生成编译规则 1. ...

  8. JVM参数配置的线上教训

    原来规则处理业务五十台服务器经常大量fgc,load飙高,我修改了jvm配置后,五十台服务器十多天没有任何异常,双十一中轻闲度过. 可是今天突然又有一台大量fgc,load飙高.分析了半天,回头一看, ...

  9. 记录一次线上yarn RM频繁切换的故障

    周末一大早被报警惊醒,rm频繁切换 急急忙忙排查 看到两处错误日志 错误信息1 ervation <memory:0, vCores:0> 2019-12-21 11:51:57,781 ...

随机推荐

  1. 论文阅读 | Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition

    源地址 arXiv:1712.07465: Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition ...

  2. 批归一化(Batch Normalization)

    之前在几篇博客中说到了权重共享,但都觉得不够全面,这里做个专题,以后有新的理解都在此更新. 1. 减少运算只是锦上添花之前说到权重共享可以减少运算,是的,但这样说好像是可有可无,只是运算量大小的问题, ...

  3. JVM探究之 —— 垃圾回收(一)

    垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和 ...

  4. Java8相关底层

    Java8是往并行方向走的.由面向对象到函数式编程. 在支持函数式编程的同时还可以支持面向对象的开发. 在JDK1.8里面,接口里面可以有实现方法的!默认方法,default.实现这个接口. 接口里面 ...

  5. [整理] linux ubuntu 服务器键盘设置错误 完美解决

    根据 原文来源:https://blog.csdn.net/mingjie1212/article/details/48525095 进行修改. 使用命令 dpkg-reconfigure keybo ...

  6. Qt开发经验小技巧71-80

    在我们使用QList.QStringList.QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍<C++ GUI ...

  7. PhpStorm2019 代码自动换行

    我的版本是2019.1里面没有了Use soft wrap in editor 所以在这里新加上*.php;*.html,并且在前面选上打勾就可以了,其余类似编辑器使用类似的方法

  8. 工控随笔_25_西门子TIA 博图V14.SP1安装报错,授权错误

    前面有一篇文章说过西门子的软件安装的时候太麻烦,很容易出现错误. 但是有些错误在安装的时候却没有关系,例如下面的错误. 如上图所示,安装已经到最后一步,总结前面的修改系统组态已经打勾(✔) ,而且提示 ...

  9. 元素高度变化使用动画transition

    高度变化,使用transition,没有效果,可以使用max-height替换. 思路: 初始元素max-height:0; 不显示,父元素hover时,重新设置元素的max-height的值, 可以 ...

  10. 【Maven学习】定制库到Maven本地资源库

    目标:手工操作将一个jar安装到本地仓库 第一步:首先获取到jar包,可以是第三方的 也可以是自己创建的,放到本地任意目录 比如:joda-time-2.10.3,放到C:\jar\  目录下面 第二 ...