Java类应用高内存问题排查指南
Java类应用的高内存现象,常常易引发大面积交易响应缓慢,系统吞吐率大幅下降,更有甚至是引起一连串关联系统的塌方式下线,一旦出现容易诱发较为严重的生产事故。本着保证业务连续性第一优先的原则,本指南从整体思路上,指导开发者快速定位问题根源、以期快速解决高内存问题,尽快恢复系统业务。
当Java应用出现高内存问题时,通常有以下两种现象:
1、JVM堆内存一直居高不下,应用未完全宕掉,Java进程存在,可以接收部分交易请求,但是交易响应非常慢,Java进程占据CPU也开始升高,应用等待较长时间后,内存被垃圾回收,应用可自行恢复。
2、JVM堆内存直接溢出,应用宕掉,Java进程还在,但是不能接收任何交易请求,应用日志中会出现OutOfMemoryError类似的日志输出,应用必须重启才能恢复。
以上两种情况,都会导致服务不可用,需要快速保护现场,采取措施后恢复业务服务。
高内存现象原因分析
生产环境突发的JVM高内存,一般都是由于部分交易从数据库一次性查询如百万级别的超大量数据、特殊场景触发了死循环BUG、大量文件操作放入内存、大量流类数据未及时关闭或释放等问题导致。
此时都会引起JVM中大量创建对象,一方面会导致内存不足,导致正常交易无内存可用;另一方面会触发不停的GC,引起CPU一直处理GC(死循环场景,还会耗在处理死循环逻辑中),导致正常交易无CPU可用,正常交易响应速度将大幅度下降;甚至如果GC的速度慢于创建对象的速度,就会导致内存溢出,服务完全宕掉。
问题快速排查流程
1.总体处理流程
2、排查前处理流程
3、问题排查流程
问题快速处理和排查步骤
本着首先保证业务连续性前提下,再进行问题排查解决为原则,可按照如下步骤进行处理和排查。
第一步:确定问题及问题发生范围
方式1:使用【微服务监控】原心小程序确定问题
借助于微服务监控原心小程序,提供的微服务状态信息展示,如CPU使用率、JVM堆栈情况、接口运行情况、接口响应情况等信息,可确定问题是否发生及问题发生范围。
如下图,如果接口运行情况中对应实例的响应时间、接口响应情况,都大范围超出常规状态,再查看疑似出问题服务器的【JVM堆栈】情况,如果其JVM堆栈曲线出现大幅上升,并保持高位,则判定此服务出现高内存问题。
方式2:使用微服务监控Grafana面板
受限于移动设备的屏幕大小,在小程序中进行更深入的分析,将比较困难。而PC端的Grafana监控界面,可更加便捷清晰的查看服务运行情况。
在【微服务运行情况】页面,可根据环境、微服务名称、实例IP等信息分别查看各微服务实例的运行状态,如果接口接口响应情况中,常用接口也都大范围超出常规的耗时时间,则说明系统疑似出现问题。
再通过【微服务JVM情况】页面,根据环境、微服务名称、实例IP等信息确定微服务实例,查看实例堆内存【Memory area】视图,查看疑似出问题服务器的【JVM堆栈】情况,如果其JVM堆栈曲线出现大幅上升,并保持高位,结合接口响应情况,则判定此服务出现高内存问题。
方式3:使用TOP命令
除了使用具备图形界面的监控程序,也可在应用所在服务器执行linux的top命令,观察java应用占用的内存情况。
1、执行top命令 2、按小写字母c,展示每个进程的详细信息 3、按大写字母M,按照内存占用从大到小排序 |
TOP命令结果重要列解释:
(1)RES列表示Resident Memory Size常驻内存大小,表名此进程使用过的最大物理内存为1.105g
(2)%CPU列展示当前进程使用了118%的CPU,在多核心服务器中,100%相当于占满了一个CPU,超过100%相当于占用超过一个CPU核心
(3)COMMAND列展示此java进程的详细信息
如下图,如果RES列已明显超过最大堆内存的配置Xmx,且CPU长时间高位运行,判定当前JAVA应用确实处于高内存状态。
第二步:隔离问题应用,快速恢复业务
根据第一步的排查,确定应用服务中的部分实例出现问题后,立马汇报决策:
(1) 确定保证交易正常运行需要保留的应用节点数量
(2) 是否可以通过隔离的方式,保留部分问题应用,以用于问题排查
(3) 决策是否需要先重启未隔离的问题应用,保证业务正常处理
如果需要快速隔离问题应用,单体系统可以通过F5隔离问题应用,转移交易流量;
微服务系统可以借助于微服务平台,在微服务平台管理端,隔离问题应用实例,转移交易流量。
第三步:判断问题应用是否已出现内存溢出
在确定不影响业务正常进行的情况下,继续进行问题排查。则需要首先判断问题应用是否已出现内存溢出的情况。如果未出现内存溢出,则可以参照【第四步】使用诊断分析平台进行问题自动排查,如果已经出现内存溢出,则需要参照【第五步】进行手动问题排查。
一般的,判断问题应用是否已经出现内存溢出有如下两种方式。
方式1:通过统一日志平台搜索日志
统一日志平台以线上化、聚合化、智能化的方式,提供在线日志排查和分析的方法,日志搜索更加便捷和快速。如果系统已经对接【统一日志平台】,则可使用统一日志平台直接搜索所有应用实例的相关日志,快速定位是否出现内存溢出现象。
通过统一日志平台【日志精析】模块,提供的跨索引查询、条件查询等功能,可以详细搜索应用各实例、各时间段的详细日志,日志不仅有高亮展示、日志上下文展示,也具备按照规则格式化展示的功能,可清晰、快捷的查找到目标日志内容。
在统一日志平台,如果搜索到【OutOfMemoryError】相关的文字,则说明此台服务器已经发生了内存溢出现象,则需要直接参照【第五步】通过堆内存情况判断造成内存溢出的问题根源。
方式2:通过日志文件判断
通过日志文件判断,则需要借助于命令行进行搜索匹配。在日志所在目录中,执行如下linux命令,可在当前目录及其子目录中,递归搜索是否有内存溢出相关日志文件:
grep -r OutOfMemoryError * |
如果在日志文件中,搜索到【OutOfMemoryError】相关的文字,则说明此台服务器已经发生了内存溢出现象,则需要直接参照【第五步】通过堆内存情况判断造成内存溢出的问题根源。
第四步:使用诊断分析平台定位内存大对象
诊断分析平台对业务无侵入,让线上应用不再是黑盒,可以让开发者实时观测应用的内部,有效辅助问题排查。如果通过日志判断未发生内存溢出现象,则可以直接使用【诊断分析平台】快速定位问题根源。
根据诊断分析平台首页中的快速接入流程,可自主接入和开启对目标应用的远程诊断。通过诊断分析平台提供的内存分析、火焰图等功能,可分析JVM中当前有哪些大对象,以及目前哪些代码方法正在大量使用JVM内存。
其中内存分析功能与jmap -histo类似,可以更加清晰的展示出当前JVM中的大对象,明确展示哪些类的对象数和占用内存空间比较高。
而火焰图功能可以实时统计JVM中申请内存的代码块,即可以直观看到申请内存的代码以及调用堆栈,可以快速定位正在发生频繁GC的问题代码。火焰图从两个纬度观察,X轴表示抽样数,即方法块越宽表示申请内存越多;Y轴表示方法的调用链路,从下到上即是整个方法的调用链路,根据调用链路,就可以看出具体哪些方法在频繁申请内存。
第五步:通过堆内存判断占用内存的大对象
如果通过已经判断出现了内存溢出现象,或者应用已假死,已经无法使用诊断分析平台正常连接应用。则应立即使用命令查看当前堆内存占用情况,甚至导出堆内存文件进行分析。
01通过histo方法查看实时堆内存大对象
使用jmap命令提供的histo方法,可查看占用内存最大的对象列表,命令格式如下:
jmap -histo:live [java应用进程号] | head -n 50 示例: jmap -histo:live 10822|head -n 50 |
在服务器使用应用对应的用户,执行上述命令,命令结果是按照内存占用大小排序,占用最大的排在最上边。可以在打印出的列表中,快速找到当前占用内存较大的对象,通过业务包名等相关关键字,定位与应用相关的对象。
02使用dump导出堆内存文件进行分析
如果在命令行中,无法明确定位问题对象,则需要导出Java的堆内存文件。可使用jmap命令提供的dump方法,导出java当前堆内存,命令格式如下。一般导出堆内存耗时比较久,导出的堆内存也较大,常常在G或10G级别。
jmap -dump:format=b,file=[导出文件存放的路径.hprof] [java应用的进程号] 示例: jmap -dump:format=b,file=/tmp/heamdump.hprof 10822 |
在服务器使用应用对应的用户,执行上述命令如下图:
方式1:通过jvisualvm
使用jdk自带的jvisualvm.exe命令,可以分析堆内存:
打开jvisualvm,装载堆内存文件后
通过其类视图,可快速看到哪些类的实例占用了大量内存,定位与业务相关的类。
再查看类的实例对象数据信息:
(1)双击内存使用最大的类,可以看到该类的所有实例对象,点击其中一个实例对象,可以看到实例对象的所有Field,基于这些信息可以初步判定可能是哪部分的代码存在问题。
(2)右下角部分展示的该对象的被引用路径,即该对象是被其他哪个对象引用的,对this节点右键选择查看最近的回收根节点,结合引用关系可以判定出何处产生了大量的对象。
方式2:通过Eclipse MemoryAnalyzer
Eclipse出品的MemoryAnalyzer也是分析堆内存比较方便的工具,其具有自动检测内存溢出问题的功能。使用程序打开堆内存文件后,会自动弹出是否自动检测内存问题“Leak Suspects Report”,选中后点击Finish。
工具将会自动分析堆内存,将可能出现的占用内存较多的问题展示出来,如下图。
在检测出的疑似问题详情中,即可看到占用内存量最大的对象及其引用关系,如下图的示例中,Parts对象占用大量内存,而Parts对象是被PartsResource引用,即可找到问题根源。
第六步:定位问题交易
根据以上步骤排查到的占用内存较大的【类】或【对象】后,需要继续分析应用交易日志,根据问题时间段内应用中发生的交易情况,判断在出现高内存的时间段,哪些交易会操作这些类或对象。
方式1:通过统一日志平台搜索日志
如果系统已经对接【统一日志平台】,则可使用统一日志平台直接搜索所有应用实例的相关日志,快速搜索疑似交易。基于平台提供的【日志精析】功能,在应用所有日志中搜索与问题相关的关键字等信息,以定位问题交易。
方式2:通过日志文件判断
cat XXX_2021112416_0.log |grep 请求路径 |
第七步:定位交易入口
基于中原银行统一开发平台研发的系统,所有前端页面发起的交易,平台会在http请求中添加交易的前端调用链路信息,这些信息也会记录在平台的后端日志中,通过调用链路相关日志即可判定交易入口页面。
而对于系统间纯接口类交易,则需要根据排查到的交易URL及相关业务信息,来确定交易的来源和交易入口。
第八步:解除隔离应用
排查问题完成后,需要重启问题应用,再根据之前隔离的问题应用,解除其隔离状态,将流量恢复到所有应用。
单体系统可以通过F5解除问题应用的隔离,恢复交易流量。
微服务系统,可以借助于微服务平台,在微服务平台管理端,解除问题应用的隔离,恢复交易流量。