目录

  • 问题报告
  • 应用概况
  • 初步分析
    • Java堆内存,MetaSpace等使用情况
    • Java堆外内存分析
    • 进程原生内存分析(Native Memory)
  • 原生内存详细分析
    • 内存内容分析
    • 引入jemalloc
  • java.util.zip.Inflater.inflateBytes调用栈跟踪
  • Kylin JDBC Driver内存泄露分析
  • 结论

问题报告

一日运维报告某应用JVM进程被OOM kill
[日期] 10-15-108-158 kernel: Out of memory: Kill process 23537 (java) score 846 or sacrifice child [日期] 10-15-108-158 kernel: Killed process 23537, UID 501, (java) total-vm:6458012kB, anon-rss:3364716kB, file-rss:112kB

可以看出进程是在虚拟内存用到6.46G左右,驻留内存用到3.36G左右被OOM kill掉的。
##问题分析

应用概况

该JVM是一个大数据接口程序,供后台报表应用查询一些预计算生成的各种数据,Linux虚拟机配置了6GB内存。应用使用了大量的第三方库连接MySQL,Kylin,Druid和ES等。同时JVM启动参数配置了XX:+HeapDumpOnOutOfMemoryError选项。

初步分析

Java堆内存,MetaSpace等使用情况

从运维Zabbix系统中可以查出此JVM运行几周的堆内存和GC情况相当正常,并无堆内存泄露的情况。也并无hprof后缀的heap dump生成。
同时Meta Space也无任何泄露迹象。
监控记录显示Java线程数目也正常,并未发生线程数量剧增导致过量使用Thread Stack内存导致的泄露。

Java堆外内存分析

接着怀疑Java应用中是不是有第三方库使用了DirectByteBuffer操作堆外内存导致的泄露。但因为并未监控JVM堆外内存的使用情况,所以需要重新采集。
于是在重启的JVM启动参数中加入-XX:NativeMemoryTracking=detail,并用crontab每小时运行jcmd {PID} VM.native_memory detail > nmt.$PID.`date '+%Y-%m-%d.%H:%M:%S'`.log
来记录JVM管辖内存的变化。也运行
pmap -x $PID > pmapx.$PID.`date '+%Y-%m-%d.%H:%M:%S'`.log
来记录操作系统进程内存段分布情况。

经记录,发现Native Memory Tracking(NMT)记录的JVM管理的内存情况均正常,包括Internal部分反映的堆外内存使用情况均正常,如下所示:
- Internal (reserved=13043KB, committed=13043KB) (malloc=13011KB #19159) (mmap: reserved=32KB, committed=32KB)
既然JVM管理的内存均正常,那就只能怀疑是进程的原生内存发生了泄露。

进程原生内存分析(Native Memory)

通过上一个步骤每小时记录的pmap内存使用和分段情况可以看出,进程使用的虚拟内存和驻留(RSS)内存不断地增加,一周虚拟内存增加了2GB多。

total kB         3174032  657612  637292
total kB         3174032  681068  660744
total kB         3174032  707084  686760
total kB         3175060  732216  711884
total kB         3175060  766008  745676
.......省略几万字
total kB         5006796 3231676 3218728
total kB         5072332 3214132 3201180
total kB         5137868 3217816 3204860
total kB         5203404 3220000 3207032

在最新的pmap输出中,可以发现大概有70多个加起来是65536kB(64MB)的内存块,随着时间推移越来越多,最后导致内存不够。

00007f756c000000    1636    1540    1540 rw---    [ anon ]
00007f756c199000   63900       0       0 -----    [ anon ]
00007f7574000000    2552    1720    1720 rw---    [ anon ]
00007f757427e000   62984       0       0 -----    [ anon ]
00007f7578000000    2024    1900    1900 rw---    [ anon ]
00007f75781fa000   63512       0       0 -----    [ anon ]
00007f757c000000   14468   14436   14436 rw---    [ anon ]
00007f757ce21000   51068       0       0 -----    [ anon ]
00007f7580000000    4452    4420    4420 rw---    [ anon ]
00007f7580459000   61084       0       0 -----    [ anon ]
00007f7584000000   22908   22852   22852 rw---    [ anon ]
00007f758565f000   42628       0       0 -----    [ anon ]
00007f7588000000   51776   51584   51584 rw---    [ anon ]
00007f758b290000   13760       0       0 -----    [ anon ]
00007f758c000000   54680   54620   54620 rw---    [ anon ]
00007f758f566000   10856       0       0 -----    [ anon ]
00007f7590000000   44616   44544   44544 rw---    [ anon ]
00007f7592b92000   20920       0       0 -----    [ anon ]
00007f7594000000   65512   65484   65484 rw---    [ anon ]

而且这些64M的内存空间都不在Native Memory Tracking输出的地址段内,不属于JVM管理的内存。

原生内存详细分析

内存内容分析

使用gdb 打印出这些内存区域的内容,起始地址使用pmap输出的内存块地址起始值,结束地址将起始地址加上想输出的内存区大小。
gdb --batch --pid {PID} -ex "dump memory native_memory.dump 0x00007f7588000000 0x00007f7588001A40" 注意起始和终止地址要加上0x表示16进制
查看native_memory.dump文件,发现是

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#driver.name=Kylin JDBC Driver
driver.version=0.1
product.name=Kylin
product.version=0.1
jdbc.compliant=true
driver.version.major=0
driver.version.minor=8
database.version.major=0
database.version.minor=8
build.timestamp=20140918-2017ication Authority - G21:08U1(c) 1998 VeriSign, Inc. - For authorized use only10U
280801235959Z01                                                                                            VeriSign Trust Network00    UUS10U
VeriSign, Inc.1<0:U3Class 1 Public Primary Certification Authority - G21:08U1(c) 1998 VeriSign, Inc. - For authorized use only10U

使用脚本统计多个64M内存块,发现90%都是类似的内容,那基本上可以定位是所使用的Kylin第三方库引入的内存泄露。由于原生内存一般是通过malloc来申请内存,所以想通过malloc的profile工具来定位具体的内存分配模块。

引入jemalloc

jemalloc的jeprof功能可以定位到原生内存的分配代码栈。通过以下步骤引入jeprof。

  • wget https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2
  • tar xvf jemalloc-5.1.0.tar.bz2
  • cd jemalloc-5.1.0
  • ./configure --enable-prof --prefix=/usr/local
  • make
  • sudo make install
  • 在启动脚本中加入export MALLOC_CONF=“prof:true,lg_prof_sample:1,lg_prof_interval:26,prof_prefix:/{YOUR INTENDED FOLDER}/jeprof.out” to run.sh
  • 同时在java命令前加上 LD_PRELOAD="/usr/local/lib/libjemalloc.so", 变成LD_PRELOAD="/usr/local/lib/libjemalloc.so" java -Xmx… -Xms…
  • 启动JVM
  • 在Linux上,cd /{YOUR INTENDED FOLDER}/,
  • 执行 jeprof --show_bytes --dot which java jeprof.out.xxxx.heap > leak_test.dot ,
    jeprof.out.xxxx.heap是最新的jemalloc生成的内存统计分析.
  • 下载leak_test.dot 到本地folder {somepath},
  • 下载graphviz(https://www.graphviz.org/download/)
  • 执行dot -Tpng {somepath}/leak_test.dot -o {somepath}/leak_test.png


可以看到大部分泄露是来自于java.util.zip.Inflater.inflateBytes

java.util.zip.Inflater.inflateBytes调用栈跟踪

使用阿里巴巴的https://alibaba.github.io/arthas/ 工具,attach到JVM,然后使用stack命令,可以看到以下调用栈。

$ options unsafe true
NAME    BEFORE-VALUE  AFTER-VALUE
-----------------------------------
unsafe  false         true
$ stack java.util.zip.Inflater inflate
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:2) cost in 254 ms.
ts=2019-03-24 10:20:58;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@18b4aac2@java.util.zip.InflaterInputStream.read()at java.io.FilterInputStream.read(FilterInputStream.java:133)at java.io.FilterInputStream.read(FilterInputStream.java:107)at java.util.Properties$LineReader.readLine(Properties.java:435)at java.util.Properties.load0(Properties.java:353)at java.util.Properties.load(Properties.java:341)at org.apache.kylin.jdbc.shaded.org.apache.calcite.avatica.DriverVersion.load(DriverVersion.java:104) at org.apache.kylin.jdbc.Driver.createDriverVersion(Driver.java:88)at org.apache.kylin.jdbc.shaded.org.apache.calcite.avatica.UnregisteredDriver.<init>(UnregisteredDriver.java:56) at org.apache.kylin.jdbc.Driver.<init>(Driver.java:70)at sun.reflect.GeneratedConstructorAccessor14.newInstance(null:-1)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at java.lang.Class.newInstance(Class.java:442)at xxxx.util.KylinUtil.getConnection(KylinUtil.java:18)

最后发现是写业务代码的同事非常错误地重复地在获取连接时新建Driver对象所导致的。
Driver driver = (Driver) Class.forName("org.apache.kylin.jdbc.Driver").newInstance();
但进一步分析,就算是重复调用也不应该有内存泄露,接着分析Kylin JDBC Driver的代码。

Kylin JDBC Driver内存泄露分析

通过查看源代码,Kylin使用了https://github.com/apache/calcite-avatica 来实现JDBC Driver,Avatica中的代码块引入了内存泄露。

try {final InputStream inStream =driverClass.getClassLoader().getResourceAsStream(resourceName);
//      inStream没有close

重复调用这段代码放大了内存泄露的规模。已经给开源项目提了issue和pull request,下个版本应该会修复这个问题。

结论

这次内存泄露使用多种工具,最后定位到了JVM原生内存的泄露根源。 技术人员碰到每一次问题,都是一次学习的机会,要沉下心来,剥丝去茧,一步步尝试去触及问题的本质,去享受云开见月明那最后一刻的喜悦。

记一次线上JVM原生系统内存泄露排查相关推荐

  1. 记一次线上请求偶尔变慢的排查

    前言 最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章. Bug现场 这是一个偶发的性能问题.在每天几百万比交易请求中,平均耗时大约为300ms,但总有那么100多笔会 ...

  2. 【好记性不如烂笔头】记一次线上问题,系统卡爆了,排查日志发现出现了ownerThread current state is WAITING, current stackTrace

    问题描述   项目部署在linux环境,运行期间页面访问特别卡,功能无法正常使用. 原因分析:   排查后台日志发现出现了"ownerThread current state is WAIT ...

  3. 记一次线上coredump事故

    转自:http://www.likecs.com/show-16439.html 记一次线上coredump事故 1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发 ...

  4. 面试官:说一下你们线上JVM是如何优化的?一不小心聊了2个小时!!

    面试官:说一下你们线上JVM是如何优化的?一不小心聊了2个小时!! 说一JVM的内存模型是什么样子的?什么时候对象可以被收回?常见的垃圾回收器算法有哪些,各有什么优劣?什么时候对象会进入老年代?什么是 ...

  5. 记几次 [线上环境] Dubbo 线程池占满原因分析(第三次:GC STW)

    [线上环境] Dubbo 线程池占满原因排查系列 记几次 [线上环境] Dubbo 线程池占满原因分析(第一次:HttpClient) 记几次 [线上环境] Dubbo 线程池占满原因分析(第二次:C ...

  6. 面试两连击:线上JVM GC问题和OOM内存溢出的解决方案,聊聊呗!

    点击上方石杉的架构笔记,右上角选择"设为星标" 每日早8点半,技术文章准时送上 公众号后台回复"学习",获取作者独家秘制精品资料 往期文章 BAT 面试官是如何 ...

  7. 有赞线上拨测系统实践(一)

    前言 一直以来,作为互联网软件工程师接触最多的事务之一便是持续集成(Continuous integration,简称 CI).持续集成俨然已成为主流互联网软件开发流程中一个重要的环节.现今有赞内部在 ...

  8. 记一次线上环境 redis偶尔连接超时报错 解决

    记一次线上环境 redis偶尔连接超时报错 解决 贴出本地控制台日志 说实话,很痛苦,跟进很久了,一直认为的jvm程序所使用的配置的连接池框架问题 因为程序为 springboot 2 spring ...

  9. 记一次线上应用连接池满的处理

    记一次线上应用dubbo-claim连接池满的处理 首先看到dubbo-claim应用突然大面积报错,基本反馈是冻结预算出问题了,看了看冻结预算的代码,发现写的非常复杂,果断放弃看代码来排查问题. C ...

最新文章

  1. svn清理失败且路径显示乱码
  2. controller的frame
  3. Servlet学习笔记(七)—— 自己定义过滤器的编写改进:自己定义实现FilterChain...
  4. 为什么Nginx性能比Apache高
  5. Technical attribute VS Read only attribute
  6. 宽客的人amp;amp;事件映射
  7. 高效研发运维体系构建的流程和方法论
  8. 前端中心化管理API使用说明
  9. mysql not exists优化_MySQL优化--NOT EXISTS和LEFT JOIN方式差异
  10. 最新剑指offer刷题笔记js(含新旧俩版所有题)
  11. C# 14位日期型字符串yyyyMMddHHmmss转变为日期格式
  12. ACM之java速成
  13. 金字塔 2020-12-29
  14. Java程序设计(二)
  15. 直接使用Jlink仿真器下载程序至Freescale K60系列 MCU
  16. 人机猜拳代码python_python 实现人和电脑猜拳的示例代码
  17. 异方差性以及加权最小二乘优化
  18. 高德地图Demo,生成apk发布到手机签名不一致
  19. 编写一个类Shop(商店)、内部类InnerCoupons(内部购物券)
  20. npm太慢, 淘宝npm镜像使用方法

热门文章

  1. linux resin 内存溢出,Resin服务器内存修改
  2. java freemarker word_Java 用Freemarker完美导出word文档(带图片)(示例代码)
  3. MyBatisPlus--多数据源
  4. ios打包报错: DXT1 compressed textures are not supported when publishing to iPhone
  5. NahimicSvc64.exe逻辑炸弹导致CPU直飚99%
  6. echarts折线图实现滑动平移,横坐标过多时隐藏部分横坐标,通过滑动平移显示隐藏的部分
  7. GameBoy系列(一):基本参数
  8. SUSCTF 2022圆满落幕,SU战队夺冠
  9. Nestlé Skin Health的医疗解决方案公司Galderma发布nemolizumab治疗中重度异位性皮炎2b期研究阳性结果
  10. kpu 处理器_深度学习及 KPU 基础知识