近期遇到一个堆外内存导致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. /etc/resolv.conf options rotate

    timeout:n sets  the  amount  of time the resolver will wait for a response from a remote name server ...

  2. VS Code中python代码自动格式化 代码自动检查

    VS Code菜单栏中依次打开 File—Preferences—Settings,搜索框中搜索“python.formatting.provider”, 然后在下拉菜单中选择autopep8,yap ...

  3. vs2017下载安装

    https://docs.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes

  4. C++11版本不能使用一个单行命名空间方式特化一个函数的bug

    warning: specialization of ‘template<class _Iterator> struct std::iterator_traits’ in differen ...

  5. 003 docker安装nginx

    一:安装与运行nginx 1.查找镜像网站 https://c.163yun.com/hub#/m/home/ 2.pull 3.查看当前在运行的容器 docker ps 4.启动nginx 使用后台 ...

  6. 【转载】 TensorFlow函数:tf.Session()和tf.Session().as_default()的区别

    原文地址: https://blog.csdn.net/Enchanted_ZhouH/article/details/77571939 ------------------------------- ...

  7. jzy3D从入门到弃坑_4尝试使用jzy3D1.0画图失败

    jzy3D从入门到弃坑_4 尝试使用jzy3D1.0画图失败 觉得有用的话,欢迎一起讨论相互学习~Follow Me 记录一下使用jzy3D1.0失败 究其原因在于 本人才疏学浅,对于JAVA ope ...

  8. python网络编程 - tcp

    网络编程 低级别的网络服务 高级别的网络服务 socket又称“套接字”,应用程序通过“套接字”向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯. tcp 传输控制协议(Tra ...

  9. Python - Django - 中间件 process_response

    process_response 函数是执行完 views.py 后执行的函数 process_response 函数有两个参数,一个是 request,一个是 response,response 是 ...

  10. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器解决方案之多方式虚拟直播

    EasyDSS流媒体解决方案之虚拟直播 虚拟直播相对于传统的实时直播的差别在于,实时的直播在于播放的是一个实时的直播流,而虚拟直播,可能适应范围更加的广.虚拟直播,不仅仅可以播放实时的视频直播流,也可 ...