概述

有挺长一段时间没写技术文章了,正好这两天美团kafka团队有位小伙伴加了我微信,然后咨询了一个JVM crash的问题,大家对crash的问题都比较无奈,因为没有现场,信息量不多,碰到这类问题我们应该怎么去分析,我想趁这次机会和大家分享一下我针对这种问题的分析过程。

不过我觉得这个分析过程可能会有点绕,因为crash文件太大,不能让大家看到crash文件的全貌而对让大家理解起来没那么有体感,因此您可以先跳到文章最后看一下结论,再回过头来看整个分析过程或许会更有帮助。

问题初探

首先我要了crash文件,第一眼当然是看开头

从这个提示初步来看我们基本猜到是物理内存不够导致的crash,在请求196608bytes内存的时候crash了,于是马上跑到crash文件的最后

发现其实物理内存是足够的,还有1G多,怎么会分配192KB的内存都分配不了呢?因此不太可能是物理内存不够导致的。

另外我们注意下这是发生在JDK7上的,JDK8上的提示会有一些不一样,JDK8上只会在32位的机器上才会有和上面完全一样的提示内容。

找出问题现场

那接下来我们要找一下发生问题的地方究竟在哪里?我的第一感觉是看是什么线程上发生的crash,然后再看其栈到底是什么?于是在crash日志里找到了如下内容:

从上面我们看到crash的线程是VMThread(JVM里唯一的一个线程,主要用来处理JVM的一些事件,比如GC),它正在做Full GC,从栈上我们可以看到正在做扩容,不过ConcurrentMarkSweepGeneration的数据结构在CMS GC下Perm和Old都是用的它,那究竟是哪个呢,我们先看下栈上提到的GenCollectedHeap::do_collection这个方法,找到其调用compute_new_size方法的地方

我们暂时无法确认到底调用的是上面那个compute_new_size,还是下面那个,如果是下面那个,那就可以确认是Perm扩容所致,如果是上面那个,那就是old扩容所致。

为了进一步确认,我们此时应该看下JVM参数

从上面的JVM参数可以看到我们设置了Xmx和Xms相等,并且还设置了Xmn,那Old的Size是固定的,这样一来基本可以排除是Old扩容所致,并且我们发现没有设置PermSizeMaxPermSize,这样肯定是存在扩容操作的,因为它们默认不相等,因此可以断定是Perm扩容的时候发生了crash

至此我们可以确定当时的场景是:发生了一次Full GC,在Full GC完之后根据当时的实际情况会对各个内存分代重新设置分代大小进行扩容,在对Perm扩容的时候发生了crash,Perm扩容其实就是做了一次mmap的操作:

从上面的代码,我们进入到warn_fail_commit_memory这个方法

理论上我们其实是会在标准输出里打印上面的内容,不过从业务方那得知,他们将标准输出重定向到了/dev/null,因此上面那行日志就看不到了,如果能看到上面的日志,那至少我们知道mmap发生的具体errno是什么,从而断定mmap到底是为什么失败了。于是叫业务方将标准输出重定向到一个固定的文件里看具体的异常。

到现在好像基本上断了思路,只能等待参数修改之后看看其效果。

结合GC日志再探问题

我忙完一些事情后又想起这个案例,于是继续看这个crash日志,看到了crash的那个点,是发生了Full GC,而这个Full GC是GenCollectFull,这个就比较特殊了,因为它发生的场景主要是三种:

  • JVMTI的强制GC
  • System GC
  • GC Locker

第一个基本可以排除,因为一般情况下都不会有这种agent来做这个,那下面两种情况怎么确定呢?如果有GC日志就好了,于是找业务方要了GC日志,比较可惜的是这个系统跑在JDK7上,GCCause并没有默认开启,因此我们从GC日志上看不到GC的原因,如果是JDK8,那在GC日志里是能看到System.gc的关键字来表明是System.gc触发的,GC Locker也同样可以看到相关的关键字。不过因为JDK参数里没有显示开启GCCause(-XX:+PrintGCCause),于是最后那条GC日志我们只能看到如下内容:

这条GC日志还没有打完,因为此时发生了crash,从这个GC日志看,老生代使用率其实很低的(5891510K/20971520K=28%),是不是超过了CMS的阈值?于是我们看上面的JVM参数里果然看到了CMSInitiatingOccupancyFraction设置为30,那就是老生代使用率达到30%会触发一次background CMS GC,不过目前还没达到CMS GC发生的条件,而且从整个GC日志来看,CMS GC的次数并不多,因此基本可以排除内存碎片的问题,因为这次要申请的内存其实还是很小的,那这么一说是不是可能是GC Locker导致的呢,看起来也不应该,这个要结合上一条GC日志来看,上一条GC日志是

和下一条Full GC相差了蛮久(2069977.519-2069794.281=183.238s),这期间新生代和老生代使用率都不高,可以结合crash日志里的下面内容断定

在Full GC之前,eden使用率也只有55%,因此不太可能在这个期间存在跨GC的情况,因此GC Locker基本没可能。

那最有可能的就是System GC了,那什么情况会导致System GC的发生呢?

稍微总结下以前主要碰到的几种会触发System GC的情况:

  • 代码里显示调用
  • RMI里定时调用
  • 堆外内存不够导致的调用

这几种可能基本都可以排除

  • 系统是kafka,是scala写的,我特地下载了scala的源码下来,基本没有这种显示调用System GC的逻辑;
  • RMI也不可能,因为我们从线程列表里看不到GC Deamon线程,因此不存在这种check的调用;
  • 堆外内存(主要是说DirectByteBuffer这种)也不太可能,因为我们从JVM参数来看,第一没有显示设置最大值MaxDirectMemorySize,第二Xmx设置有24G,那默认的堆外内存最大值基本也有差不多这么大(Xmx除去一个survivor的大小),后面再结合看了下top命令看到的数据的值,物理内存总共都才使用了7G左右,所以堆外内存满了而导致的System GC也基本可以排除。

所以我再次翻了下JDK的代码,寻找可能抛出System GC的情况,结果发现了sun.nio.ch.FileChannelImpl的map方法里有类似的逻辑

会不会是这里发生的呢?

确定问题现场

为了确定是否有类似的逻辑调用,于是我在kafka的源码里搜索了map的逻辑调用,果然看到大量的地方调用

似乎柳暗花明了,找业务方确认,还真得知有大量的索引文件映射,并且都是大于1G的,于是我们是否可以设想这么一个场景:kafka映射一个大文件,结果失败了,然后抛出了一个OOM的异常,在catch到这个OOM的异常之后,主动触发了一次System.gc,从而这就是当时crash发生的整个现场。

我们从crash日志里找到了类似的日志

这个时间点和Full GC的时间点基本一致,差不多可以认为是上面这个事件发生的时候接着做了一次Full GC,那基本可以笃定jni.cpp:743应该是一个OOM的异常处理,于是我找到这个JDK版本的这行代码的位置,验证确实如此

因此基本也确定业务系统是直到这个地方发生了crash,要验证这个只能是有那个时候的线程,比如有crash时候的heapdump或者coredump,然后找到引发Full GC的那个线程,看它的调用栈是不是正好在做这块的操作。

确定根本原因

问题发生的点确认了,那接下来我们要寻找根本原因,我们看下sun.nio.ch.FileChannelImpl.map0的native方法实现Java_sun_nio_ch_FileChannelImpl_map0,因为这是触发crash的地方,然后看到下面的逻辑

因为确实是抛出了一个OOM的异常,因此我们这里完全可以确定mmap返回的errno是ENOMEM,所以我们在前面提出的,想把标准输出打印出来以确认具体的mmap异常,从这个地方也基本可以确认mmap就是因为返回了ENOMEM这个错误码,我们先看下mmap的手册里关于ENOMEM的介绍

因为我们确认了物理内存是足够的,因此我们只能怀疑是否达到了mappings的最大个数,我们再结合下kernel里的mmap的实现(mmap.c:do_mmap)

当mmap的vma个数达到了最大值的时候确实也会返回ENOMEM,于是我们要业务方确认下/proc/sys/vm/max_map_count的值,结果发现是65530,也就是说我们mmap的vma最多只能是65530个,我们再结合下crash日志里虚拟地址映射的个数,正好达到了这个上限了,具体可以看看Dynamic libraries下面的条数有多少基本就是mmap的vma的个数。

至此我们完全确认了整个问题。

问题解决

从上面分析解决问题的方法有两个

  • 增大系统限制/proc/sys/vm/max_map_count
  • kafka的索引文件是否不需要一直有?是否可以限制一下

问题总结

上面的过程是我思考的一个过程,可能过程有些绕,不过我这里可以来个简单的概述,描述下整个问题发生的过程:

kafka做了很多索引文件的内存映射,每个索引文件占用的内存还很大,这些索引文件并且一直占着没有释放,于是随着索引文件数的增多,而慢慢达到了物理内存的一个上限,比如映射到某个索引文件的时候,物理内存还剩1G,但是我们要映射一个超过1G的文件,因此会映射失败,映射失败接着就做了一次System GC,而在System GC过程中因为PermSize和MaxPermSize不一样,从而导致了在Full GC完之后Perm进行扩容,在扩容的时候因为又调用了一次mmap,而在mmap的时候check是否达到了vma的最大上限,也就是/proc/sys/vm/max_map_count里的65530,如果超过了,就直接crash了。

这只是我从此次crash文件里能想像到的一个现场,当然其实可能会有更多的场景,只要是能触发mmap动作的地方都有可能是导致crash的案发现场,比如后面又给了我一个crash文件,是在创建线程栈的时候因为mmap而导致的crash,完全和OOM没有关系,所以根本原因还是因为kafka做了太多的索引文件映射,导致mmap的vma非常多,超过了系统的限制,从而导致了crash。

假笨说-协助美团kafka团队定位到的一个JVM Crash问题相关推荐

  1. CVPR 2022 | 美团技术团队精选论文解读

    计算机视觉国际顶会CVPR 2022近日在美国新奥尔良召开,今年美团技术团队有多篇论文被CVPR 2022收录,这些论文涵盖了模型压缩.视频目标分割.3D视觉定位.图像描述.模型安全.跨模态视频内容检 ...

  2. 【美团技术团队】2014年-2022年后端文章精选篇

    目录 2022年后端文章精选 2021年后端文章精选 2020年后端文章精选 2019年后端文章精选 2018年后端文章精选 2017年后端文章精选 2016年后端文章精选 2015年后端文章精选 2 ...

  3. 虚假新闻检测,来自美团NLP团队方案

    向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程   公众号:datayx 这篇文章主要以第二名为讨论对象,来自美团NLP团队.同时会对比第一名和第三名的方案.此外,给出 ...

  4. 美团技术团队-大众点评搜索基于知识图谱的深度学习排序实践

    美团技术团队博客网址:https://tech.meituan.com/2019/02/28/root-clause-analysis.html 1. 引言 挑战与思路 搜索是大众点评App上用户进行 ...

  5. 2021年美团技术团队最受欢迎的22篇技术文章

    再见2021 你好2022 「美团技术团队」微信公众号祝大家新年快乐!温故而知新,我们根据文章的「阅读量」和「在看」数,以及所覆盖的技术领域,精选了22篇技术文章作为新年礼物送给大家.希望在2022年 ...

  6. 【遇见Doris】4.13线下开发者沙龙分享--美团点评团队

    这次的Apache Doris (incubating) 0.10.0开发者沙龙在中关村国际会议中心圆满结束,各位嘉宾都带来了干货满满的分享.关注Doris官方公众号,后台回复"沙龙分享&q ...

  7. CVPR 2022 | 美团技术团队精选6篇优秀论文解读

    CVPR 2022 | 美团技术团队精选论文解读 计算机视觉国际顶会CVPR 2022近日在美国新奥尔良召开,今年美团技术团队有多篇论文被CVPR 2022收录,这些论文涵盖了模型压缩.视频目标分割. ...

  8. 假笨说-谨防JDK8重复类定义造成的内存泄漏

    概述 如今JDK8成了主流,大家都紧锣密鼓地进行着升级,享受着JDK8带来的各种便利,然而有时候升级并没有那么顺利?比如说今天要说的这个问题.我们都知道JDK8在内存模型上最大的改变是,放弃了Perm ...

  9. 《美团机器学习实践》高清PDF+思维导图+美团算法团队

    在美团的搜索.推荐.计算广告.风控.图像处理等领域,相关的人工智能技术得到广泛的应用.<美团机器学习实践>包括通用流程.数据挖掘.搜索和推荐.计算广告.深度学习以及算法工程6大部分内容,全 ...

最新文章

  1. CISSP考前总复习
  2. VSCode的Teams插件
  3. 【Angular专题】——【译】Angular中的ForwardRef
  4. FPGA原语使用方法
  5. MQTT工作笔记0005---CONNECT控制报文2
  6. arcgisengine计算线要素总长度_掌握K线并不难!新手请牢记K线四要素:开盘价、收盘价、最低价、最高价,看不懂别炒股...
  7. 5秒钟搭建一个简单版的restful资源服务器
  8. 优化工作的冰山一角,app瘦身
  9. Android 5.0 屏幕录制/截屏
  10. MySQL varbinary类型字段 java怎么取
  11. 会声会影2022新版本对电脑配置要求
  12. 算法学习笔记12:散列表
  13. docker下安装wekan看板工具
  14. 为什么说10月24日是程序员的节日?
  15. [Tyvj1450 GF打Dota]
  16. 07 Anykey图像优化及文字头像生成与加载
  17. Jboot发布啦,如果你做微服务架构,你应该看看。
  18. 数据库电话号码查询显示中间四位用****代替的SQL语句
  19. mysql免安装版,遇到msvcr120dll文件丢失错误的解决方案
  20. Windows常用Dos命令详解_被迫流浪者的博客

热门文章

  1. Python Argparse模块
  2. JS获取用户控件中的子控件Id
  3. cisco SMD 配置安装
  4. MATLAB二维图形坐标变换
  5. ffmpeg学习1 音视频基本概念
  6. ZooKeeper 如何保证数据一致性
  7. C/C++协程库libco:微信怎样漂亮地完成异步化改造
  8. python提交post请求payload webkit_python爬虫实现POST request payload形式的请求
  9. 打造一个实用的Ubuntu Linux
  10. Hack Knowledges