JVM问题分析处理手册

一.前言

各位开发和运维同学,在项目实施落地的过程中,尤其是使用EDAS、DRDS、MQ这些java中间件时,肯定会遇到不少JAVA程序运行和JVM的问题。我结合过去遇到的各种各样的问题和实际处理经验,总结了JAVA问题的处理方式,希望能帮助到大家。

二.问题处理总体概括

如下图所示:

问题处理分为三大类:
1.问题发生后的紧急处理原则
2.问题归类和分析
3.分析排查工具和命令
通常来说,最后的解决方式有两种,第一种是最普遍的,绝大部分的最终问题原因可以定位到代码层面,修改代码后问题解决。
第二种,调整某些JVM参数,缓解问题发生的频率和时间,但是治标不治本,所以本篇分析文档并未涉及JVM参数的优化调整。

三.紧急处理原则

问题发生后,第一时间是快速保留问题现场供后面排查定位,然后尽快恢复服务。保留现场的具体操作:

  • 打印堆栈信息,命令行:jstat -l 'java进程PID'
  • 打印内存镜像,命令行:jmap -dump:format=b,file=hprof 'java进程PID'
  • 生成core文件,命令行:gcore 'java进程PID'
  • 保留gc日志文件
  • 保留业务日志文件
  • 查看JAVA堆内存运行分配:命令行:jstat -gcutil 'java进程PID' 1000
  • 完成以上操作后,尽快重启JAVA进程或回滚,恢复服务。

四.问题归类和分析

当应用系统运行缓慢,页面加载时间变长,后台长时间无影响时,都可以参考以下归类的解决方法。绝大部分的JAVA程序运行时异常都是Full GC、OOM(java.lang.OutOfMemoryError)、线程过多。主要分这么几大类:

  • 持续发生Full GC,但是系统不抛出OOM错误
  • 堆内存溢出:java.lang.OutOfMemoryError:Java heap space
  • 线程过多:java.lang.OutOfMemoryError:unable to create new native thread
  • JAVA进程退出
  • CPU占用过高

通常来说,可以用一些常用的命令行来打印堆栈、内存使用分配、打印内存镜像文件来分析,比如jstack、jstat、jmap等。但是某些时刻,还是需要引入更高阶的代码级分析工具(比如btrace)才能定位到具体原因。针对每一种问题,我会依据具体的case来详细说明解决方式。

五.分析排查工具和命令

问题排查,除了最重要的解决思路外,合理的运用工具也能达到事半功倍的效果,某些时候用好了工具,甚至能直接定位到导致问题的具体代码。

JDK自带工具

Jstat

实时查看gc的状况,通过jstat -gcutil 可以查看new区、old区等堆空间的内存使用详情,以及gc发生的次数和时间

Jstack

很重要的命令,jstack可以用来查看Java进程里的线程都在干什么,对于应用没反应、响应非常慢等场景都有很大的帮助。几乎所有java问题排查时,我第一选择都是使用jstack命令打印线程信息。

Jmap

jmap -dump用于保存堆内存镜像文件,供后续MAT或其他的内存分析工具使用。jmap -histo:live也可以强制执行full gc。

Jinfo

通常我用它来查看Java的启动参数

gcore

我通常会使用gcore命令来保留问题现场,速度非常快。某些时候执行jstack或jmap会报错,加-F也不行,这个时候就可以用gcore命令,gcore javapid 可以生成core文件,可以通过jstack和jmap命令从core文件里导出线程和内存镜像文件。

堆内存分析工具

MAT

eclipse官方推出的本地内存分析工具,运行需要大量内存,从使用角度来讲,并不方便。我现在已经很少使用。

ZProfiler

阿里中间件出品的在线堆内存分析工具,链接是:http://zprofiler.alibaba-inc.com,不需要拷贝镜像文件,直接在线分析。

EagleEye-MProf

也是阿里中间件团队推出的,适用于复杂云环境的轻量级java堆内存分析工具,非常好用。在公共云或者专有云的机器上,运行的是客户的机器。由于权限或者网络的关系,我们很难去执行jmap进行heap dump,或者scp上传dump文件。这个工具可以直接在ECS上分析,而不用下载dump文件。

代码跟踪工具

Btrace

Java代码动态跟踪神器。少数的问题可以mat后直接看出,而多数会需要再用btrace去动态跟踪。比如排查线程创建过多的场景,通过btrace可以直接定位到哪段代码创建了大量线程。
比较麻烦的就是要自行编写脚本,不过现成的脚本也有不少,可以直接使用。

六.案例分析一:持续出现Full GC

处理原则

如果发现cms gc或full gc后,存活的对象始终很多,这种情况下可以通过jmap -dump来获取下内存dump文件,也可以通过gcore命令生成core文件,再用jmap从core里导出dump文件。然后通过MAT、ZProfile、EagleEye-MProf来分析内存镜像分布。如果还不能定位,最后使用btrace来定位到具体的原因。对于cms gc而言,还有可能会出现promotion failed或concurrent mode failure问题。

分析过程

1. 查看GC日志:

发现应用系统一直在做full gc,而且还有报错“concurrent mode failure”,这表示在执行gc过程中,有对象要晋升到旧生代,而此时旧生代空间不足造成的。

2. 查看应用访问日志:

有条很可疑的访问记录,响应时间特别长,达到了60S,直接超时了。

3. 查看JVM内存分配详情:

如何判断JVM垃圾回收是否正常,我通常会使用jstat命令,jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。可见,jstat是轻量级的、专门针对JVM的工具,非常适用。jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。
执行:cd $JAVA_HOME/bin中执行jstat,注意jstat后一定要跟参数。
具体命令行:jstat -gcutil 'java进程PID' 1000


如图所示,发现Eden区、Old区都满了,此时新创建的对象无法存放,晋升到Old区的对象也无法存放,所以系统持续出现Full GC了。

4. 分析内存镜像:

这种问题比较直观,需要dump内存镜像,然后用MAT或者Zprofiler工具分析heapdump文件。
MAT(Memory Analyzer Tool),一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 JAVA heap 分析工具,它可以帮助我们查找内存泄漏问题。
Zprofiler是阿里中间件团队推出的在线JAVA内存分析工具,不用在本地运行,访问http://zprofiler.alibaba-inc.com/ 即可。
保存内存镜像的命令:jmap -dump:format=b,file=hprof 'java进程PID',通常来讲这个文件会比较大,一般都会有好几个G。
用MAT或ZProfile分析结果如下,基本定位了问题的原因


很明显,LinkedHashMap占用了91.41%的内存,里面有大量的ThRoomQuotaDO的引用对象,很有可能是死循环造成了这样的现象。不过要寻找真实原因,还得看看具体的代码。

5. 查看源代码

查询ThRoomQuotaDO类的源代码,发现了导致死循环的代码,如下:

代码段一:
`if(start == null || end == null || start.before(now)){

start = CalendarUtil.addDate(now, 1);end = CalendarUtil.addDate(start,1);`

这里的start和end就是之前应用访问日志里的那条get请求带进来的,请求的url如下:

请注意start=2010-04-031061,本来这个start的值是不规范的,但代码还是通过SimpleDateFormat把start解析成的Date为(Fri Apr 15 00:00:00 CST 2095),所以不会进入代码段一的if块中去。

代码段二:
`Map roomQuotaMapFull = new LinkedHashMap();
for (Date d = hotelQuery.getStart(); d.compareTo(hotelQuery.getEnd()) != 0; d = CalendarUtil.addDate(d, 1)) {

ThRoomQuotaDO rq = new ThRoomQuotaDO();rq.setTheDate(d);rq.setMock();roomQuotaMapFull.put(d, rq);

}`

这里就是最终导致full gc的代码:d.compareTo(hotelQuery.getEnd()) != 0,显然start和end是不相等的,所以陷入死循环中,以致把内存塞满了。
因此,最终的原因得到了定位,要解决起来那就很容易了。

6. 解决

代码段一:
修改导致问题的代码(标红处是修改后的)
if(start == null || end == null || start.before(now))|| start.after(end)){

start = CalendarUtil.addDate(now, 1);end = CalendarUtil.addDate(start,1);

}

代码段二:
for (Date d = hotelQuery.getStart();d.compareTo(hotelQuery.getEnd()) <= 0 ;d = CalendarUtil.addDate(d, 1)) {

ThRoomQuotaDO rq = new ThRoomQuotaDO();rq.setTheDate(d);rq.setMock();roomQuotaMapFull.put(d, rq);

}

重新打包发布后,内存恢复正常,再也没有发生此类问题。

案例分析二:OOM:unable to create new native thread

处理原则

在日志中可能会看到java.lang.OutOfMemoryError: Unable to create new native thread,可以先统计下目前的线程数(例如ps -eLf | grep java -c),然后可以看看ulimit -u的限制值是多少,如线程数已经达到限制值,如限制值可调整,则可通过调整限制值来解决;如不能调限制值,或者创建的线程已经很多了,那就需要看看线程都是哪里创建出来的,首先用jstack命令打印线程堆栈信息,统计线程状态,然后通过btrace来查出是哪里创建的线程。
如果jstack执行失败的话,可以使用gcore命令生成core文件:gcore javapid > core.**
然后再用jstack和jmap可以导出堆栈和内存镜像文件。
具体命令:jstack $JAVA_HOME/bin/java core.** > jstack.log

分析过程

1. 查看GC日志:

发现gc都正常,没有full gc,new区,old区都正常,确定不是heap堆出问题。

2. 查看应用访问日志:

发现业务日志中有很多错误:

3. 查看线程堆栈:

查看jstack.log堆栈信息发现一共有8204个线程,其中blocked的有8169个,明显不正常。通过堆栈信息不能看出是什么导致创建了这么多线程,但是至少能确定导致oom的原因了:线程创建太多,且全部block了。下面就需要用btrace工具跟进分析具体是什么代码创建了这么多的线程。

4. 使用Btrace工具分析:

编写btrace脚本ThreadStart.java,内容如下:
`import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
@BTrace public class Trace{
@OnMethod(

clazz="java.lang.Thread",method="start"

)
public static void traceExecute(){

jstack();

}
}`

执行命令:./btrace javapid ThreadStart.java > thread.log
查看thread.log,到这里可以确定问题所在了,基本上所有的线程都在这个类里创建:com.taobao.et.web.ie.module.action.IeOrderCheckAction.doMileageMeta

5. 查看源代码

从代码里可以看到,每次执行doMileageMeta都会创建一个线程池,但是没有shutdown释放资源。

问题原因就清楚了:应用启动后,每次请求都会创建一个包含3个线程的线程池,但是从不释放资源,涨到8000多个以后,达到线程上限,抛出了"unable to create new native thread"。

6. 解决

修改代码:每次创建线程池后,调用shutdown方法来释放资源,具体代码就不帖出来了。

案例分析三:OOM:java heap space

处理原则

java.lang.OutOfMemoryError: java heap size也是常见的现象。最重要的是dump出内存,一种方法是通过在启动参数上增加-XX:+HeapDumpOnOutOfMemoryError,另一种方法是在当出现OOM时,通过jmap -dump获取到内存dump,在获取到内存dump文件后,可通过MAT进行分析,但通常来说仅仅靠MAT可能还不能直接定位到具体应用代码中哪个部分造成的问题,例如MAT有可能看到是某个线程创建了很大的ArrayList,但这样是不足以解决问题的,所以通常还需要借助btrace来定位到具体的代码。

案例分析四:Java进程退出

处理原则

对于Java进程退出,最重要的是要让系统自动生成coredump文件,需要设置ulimit -c unlimited,表示core文件为不限制大小,通常会加到java应用的启动脚本里。当进程crash发生时,找到coredump文件,core文件通常位于 /应用部署目录/target (core文件一般在程序工作区产生)下。有可能的原因是代码中出现了无限递归或死循环,可通过jstack来提取出Java的线程堆栈,从而具体定位到具体的代码。
如果以上方法无效时,可以尝试dmesg命令,看看系统出了什么问题,有可能是系统主动杀掉了Java进程(例如内存超出限制)

分析过程

1.生成coredump文件

在将出现crash的服务器上打开ulimit,等待生成core文件,执行命令:ulimit -c unlimited 。

2.分析coredump文件

执行命令:gdb -c $JAVA_HOME/bin/java coredump_file
键入:info threads ----- 获取core文件中线程信息
其中*号前代表是crash时,当前正在运行的线程
即当前正在运行的线程为 9406

3.使用jstack命令分析core文件,获取指定线程id对应的代码信息

具体命令:jstack $JAVA_HOME/bin/java coredump_file > jstack.log,搜索刚才得到的线程编号 9406,发现问题如下:


原因就是代码里出现了无限递归,导致了爆栈。

案例分析五:CPU占用过高

处理原则

从经验上来说,有些时候是由于频繁cms gc或fgc造成的,在gc log是记录的情况下(-Xloggc:),可通过gc log看看,如果没打开gc log,可通过jstat -gcutil来查看,如是gc频繁造成的,则可跳到后面的内存问题 | GC频繁部分看排查方法。

如不是上面的原因,可使用top -H查看线程的cpu消耗状况,这里有可能会看到有个别线程是cpu消耗的主体,这种情况通常会比较好解决,可根据top看到的线程id进行十六进制的转换,用转换出来的值和jstack出来的java线程堆栈的nid=0x[十六进制的线程id]进行关联,即可看到此线程到底在做什么动作,这个时候需要进一步的去排查到底是什么原因造成的,例如有可能是正则计算,有可能是很深的递归或循环,也有可能是错误的在并发场景使用HashMap等。

分析过程

这里分享下如何查看具体导致CPU高的线程名称
1.找到最耗CPU资源的java线程TID
执行命令:ps H -eo user,pid,ppid,tid,time,%cpu,cmd | grep java | sort -k6 -nr |head -1
2.将找到的线程TID进行十六进制的转换
printf "%xn" tid
3.从堆栈文件里找到对应的线程名称
通过命令导出堆栈文件:jstack javapid > jstack.log,根据上一步得到的线程TID,搜索堆栈文件,即可看到此线程到底在做什么动作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/636823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

soso移动营业大厅(纯后端+MySQL数据库+JDBC)

一、项目需求 中国移动,中国联通,中国电信是国内3大通信运营商,每个运营商都提供了不同的品牌套餐来应对不同的用户群,比如北京移动主要有全球通,神州行,动感地带等3大品牌套餐,每种套餐的内容和费用不同,嗖嗖移动是一个假定的通信运营商,提供了话痨套餐,网虫套餐,超人套餐,各…

等离子环制作

免责声明 在您参考该博客制作等离子环前&#xff0c;请仔细阅读以下重要安全警告和免责说明。使用本文档即表示您已充分了解并同意以下条款&#xff1a; 等离子环的危险性&#xff1a;等离子环在运行时玻璃瓶身会产生高温&#xff0c;存在低温烧伤风险。任何时候都不建议用手…

C for Graphic:Sliced Circle Image

不做UI不知道&#xff0c;没想到时至今日&#xff0c;ugui居然没有sliced filled image模式&#xff0c;用circle做filled&#xff0c;不能用sliced九宫格图&#xff0c;导致每次使用这个效果必须一张新图&#xff0c;何其浪费资源。 原始功能如下&#xff1a; 我…

【C++】unordered_map,unordered_set模拟实现

unordered_map&#xff0c;unordered_set模拟实现 插入普通迭代器const迭代器unordered_map的[ ]接口实现查找修改哈希桶完整代码unordered_map完整代码unordered_set完整代码 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 上一篇文章我们把unordered_map和u…

爬虫之Cookie获取:利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法

爬虫之Cookie获取&#xff1a;利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法 在爬虫或模拟请求时&#xff0c;特别是获取验证码的时候&#xff0c;反爬虫的网站的cookie或定期失效&#xff0c;复制出来使用是不行的为了应对这种方式&#xff0c;我们可能…

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Windows下载安装vcpkg并使用它来安装第三方库(visualstudio)

1.使用Git下载vcpkg仓库&#xff08;下载比较慢&#xff0c;个人比较喜欢打开下面网址然后用迅雷下载&#xff0c;速度飞快&#xff09; git clone "https://github.com/Microsoft/vcpkg.git"2.下载好之后解压打开文件夹&#xff0c;双击bootstrap-vcpkg.bat文件&…

中小企业如何快速融资-----股权融资的四种方式(上)

’在企业融资的多种手段中&#xff0c;股权质押融资、股权交易增值融资、股权增资扩股融资和股权的私募融资&#xff0c;逐渐成为中小企业利用股权实现融资的有效方式。随着市场体系和监管制度的完善&#xff0c;产权市场为投融资者搭建的交易平台日益成熟&#xff0c;越来越多…

ChatGPT 到 Word:使用 Writage 进行复制粘贴魔法

ChatGPT 到 Word&#xff1a;使用 Writage 进行复制粘贴魔法 写在前面Writage的使用 写在前面 随着ChatGPT的日益普及&#xff0c;越来越多的人每天依赖它来完成各种任务。无论是寻找信息、语言翻译、解决数学问题&#xff0c;还是精炼复杂的概念和文本&#xff0c;ChatGPT 都…

React里面table组件的时间轴的位置计算

如上图的时间轴位置计算 计算时间轴位置倒是容易&#xff0c;主要是React里面的antd的table组件怎么监听滚动是个问题 /*** &#xff08;月台/时间&#xff09;为150&#xff0c;时间为100&#xff0c;每个格子为120&#xff0c;120px/30分钟4px/分钟* 00:00分为250px* 00:…

muduo 网络库源码解析和使用

1. base 模块 1.1 API 1.1.1 eventfd int eventfd(unsigned int initval, int flags);&#xff08;1&#xff09;类似信号量&#xff1b;其内部保存了一个 uint64_t 计数器 count&#xff0c;使用 initval 初始化&#xff1b; &#xff08;2&#xff09;read 没有设置 EFD…

Suricata-7.0 源码分析之流表建立FlowWorker

一、什么是Flow&#xff1f; 二、Flow是怎么建立的&#xff1f; 三、Flow建立的具体过程是什么&#xff1f; 一、什么是Flow&#xff1f;   在Suricata 7.0中&#xff0c;流Flow是指所有相同五元组&#xff08;协议&#xff0c;源IP&#xff0c;目的IP&#xff0c;源端口&am…

makefile里面的变量使用,系统变量

文章目录 makefile里面的变量使用 makefile里面的变量使用 calc:add.o sub.o multi.ogcc add.o sub.o multi.o calc.cpp -o calcadd.o:add.cppgcc -c add.cpp -o add.osub.o:sub.cppgcc -c sub.cpp -o sub.omulti.o:multi.cppgcc -c multi.cpp -o multi.oclean:rm -rf *.o cal…

Linux第32步_编译ST公司的TF-A源码

正点原子STM32MP157开发板使用的CPU型号是STM32MP157DAA1&#xff0c;而开发板硬件参考了ST公司的STM32MP157 EVK开发板&#xff0c;因此我们在移植的时候需要关注“stm32mp157d-ev1”。 一、了解SDK 包 ST公司会从ARM官方下载TF-A软件包&#xff0c;然后将STM32MP1 芯片添加…

算法总结——单调栈

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 文章目录 一、单调栈的定义二、单调栈的应用&#xff1a;寻找左边第一个比它小的数单调栈的思想&#xff08;重点&#xff09;&#xff1a;寻找左边第一个比它小的数的下…

Unity常用的优化技巧集锦

Unity性能优化是面试的时候经常被问道的一些内容&#xff0c;今天给大家分享一些常用的Unity的优化技巧和思路&#xff0c;方便大家遇到问题时候参考与学习。 对啦&#xff01;这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白&#xff0c;也有一些正在从事游…

(C语言)编译和链接

前言͟͟͞͞&#x1f48c;&#xff1a;对于现在的各种编译器而言许多都是好多个功能的集成&#xff0c;那么我们的代码到底是如何去实现的呢&#xff1f;难道我们的计算机可以直接读懂我们所写的代码&#xff0c;并运行吗&#xff1f;对于很多细心的小伙伴们可能会想这样的问题…

Spring Security 优化鉴权注解:自定义鉴权注解的崭新征程

文章目录 1. 引言2. Spring Security基础2.1 Spring Security概述2.2 PreAuthorize注解 3. 自定义鉴权注解的优势3.1 业务语义更明确3.2 参数化鉴权更灵活3.3 可维护性更好 4. 实现自定义鉴权注解4.1 创建自定义注解4.2 实现鉴权逻辑4.3 注册自定义注解和逻辑4.4 使用自定义注解…

Divisibility Problem-codefordes

题目链接&#xff1a;Problem - A - Codeforces 解题思路&#xff1a; 如果 a 能被 b整除&#xff0c;就不需要进行改变&#xff0c;直接输出0&#xff0c;否则输出((a / b) 1) * b - a&#xff0c;找到最小的能被b整除的数。 下面是c代码&#xff1a; #include<iostrea…

探索JAVA神秘运行机制:揭秘JVM内存区域

目录 1. 前文回顾 2.内存区域的划分 2.1 存放类的方法区 2.2 程序计数器 2.3 Java虚拟机栈 2.4 Java堆内存 2.5 其他内存区域 3. 核心内存区域运行流程 4. 总结 1. 前文回顾 上一篇我们一起探索了Java的整体运行流程&#xff0c;类加载器以及类的加载机制&#xff0…