zoukankan      html  css  js  c++  java
  • Linux下JVM常见问题处理

    一、背景分析

    线上故障主要会包括 CPU、内存磁盘以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。基本上出问题就是 df、free、top,然后依次 使用jstack、jmap,具体问题具体分析。

    二、CPU分析

    一般来讲我们首先会排查 CPU 方面的问题。原因包括业务逻辑问题(死循环)、频繁 GC 以及上下文切换过多。而最常见的往往是业务逻辑(或者框架逻辑)导致的,可以使用 jstack 来分析对应的线程栈情况。

    1. 使用 jstack 分析线程栈

    先用 ps 命令找到对应进程的 pid有好几个目标进程,可以先 top 找到cpu较高的进程

     

    接着top -H -p <pid> 找到 CPU 使用率比较高的一些线程

     

    然后将占用最高的 pid 转换为 16 进制printf '%x ' <pid>得到 nid

     

    接着直接在 jstack 中找到相应的堆栈信息

    jstack <pid> |grep '<nid>' -C5 –color

    利用 jstack 生成虚拟机中所有线程的快照

    jstack -l {pid} > {path}

    cat {path} |grep '790D' -C 5

     

    线程快照格式都是统一的,我们以一个线程快照简单说明下

    "http-nio-9001-exec-269" #536 daemon prio=5 os_prio=0 tid=0x00007f04700eb800 nid=0x790d runnable [0x00007f0435cb4000]

       java.lang.Thread.State: RUNNABLE

     

    "http-nio-9001-exec-269"   线程名称

    #536   线程编号

    daemon   守护线程

    prio=5   线程的优先级

    os_prio=0   系统级别的线程优先级

    tid=0x00007f04700eb800   线程id

    nid=0x790d   native线程的id

    runnable [0x00007f0435cb4000]   线程当前状态

    对整个 jstack 文件进行分析,通常我们会比较关注 WAITING 和 TIMED_WAITING 的部分,如果 WAITING 之类的特别多,那么多半是有问题。

    cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c

     

    2. 频繁 GC

    先确定下 gc 是不是太频繁,使用jstat -gc pid 1000命令来对 gc 分代变化情况进行观察,1000 表示采样间隔(ms)

    jstat -gc pid 1000

     

     

     

    如果看到 gc 比较频繁,再针对 gc 方面做进一步分析。

    3. 上下文切换

    针对频繁上下文问题,我们可以使用vmstat命令来进行查看cs(context switch)一列则代表了上下文切换的次数。

     

    如果对特定的 pid 进行监控可以使用 pidstat -w pid命令cswch 和 nvcswch 表示自愿及非自愿切换。

    pidstat -w <pid>

     

     

    4. 分析过程

    (1) 找到CPU使用率最高的进程

    (2) 找到该进程中CPU使用率最高的线程

    (3) 利用jstack生成该进程下所有线程快照

    (4) 在快照中找到对应的线程分析问题

    三、磁盘

    首先是磁盘空间方面,使用df -hl来查看文件系统状态

    df -hl

     

    更多时候磁盘问题还是性能上的问题可以通过 iostat -d -k -x来进行分析

    iostat -d -k -x

     

    最后一列%util可以看到每块磁盘写入的程度,而rrqpm/s以及wrqm/s分别表示读写速度,一般就能定位到具体哪块磁盘出现问题了。

    四、内存

    主要包括 OOM、GC 问题和堆外内存。一般来先用free命令先来检查内存的各种情况。

    free

    或者

    free -h

     

    1. 堆内内存

    内存问题大多还都是堆内内存问题。表象上主要分为 OOM 和 Stack Overflow

    (1) OOM

    JMV 中的内存不足,OOM 大致可以分为以下几种:

    Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

    没有足够的内存空间给线程分配 Java 栈,基本上还是线程池代码写的有问题,比如说忘记 shutdown,JVM 方面可以通过指定Xss来减少单个 thread stack 的大小。另外也可以在系统层面增大 os 对线程的限制

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    堆的内存占用已经达到-Xmx 设置的最大值。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过 jstack 和 jmap 去定位问题。如果一切都正常需要通过调整Xmx的值来扩大内存。

    Caused by: java.lang.OutOfMemoryError: Meta space

    元数据区的内存占用已经达到XX:MaxMetaspaceSize设置的最大值,解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过 jstack 和 jmap 去定位问题参数方面可以通过XX:MaxPermSize来进行调整。

    java.lang.StackOverflowError

    栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候,请求新建栈帧时,栈所剩空间小于战帧所需空间线程栈需要的内存大于 Xss 值)。常见于通过深度递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常

    Exception in thread "main" java.lang.StackOverflowError

    (2) 使用 JMAP 定位代码内存泄漏

    关于 OOM 和 Stack Overflo 的代码排查方面,我们一般使用 JMAP来导出 dump 文件

    jmap -dump:format=b,file=filename <pid>

    MAT(Eclipse Memory Analysis Tools)

    mat独立版下载地址http://www.eclipse.org/mat/downloads.php

     

    注:安装完成后,为了更有效率的使用 MAT,可以配置一些环境参数。因为通常而言,分析一个堆转储文件需要消耗很多的堆空间,为了保证分析的效率和性能,建议分配给 MAT 尽可能多的内存资源。可以采用如下两种方式来分配内存更多的内存资源给 MAT。

    第一修改启动参数 MemoryAnalyzer.exe-vmargs -Xmx4g

    第二编辑文件 MemoryAnalyzer.ini,在里面添加类似信息 -vmargs– Xmx4g。

    1. MemoryAnalyzer.ini中的参数一般默认为-vmargs– Xmx1024m,这就够用了。假如机器的内存不大,改大该参数的值,会导致MemoryAnalyzer启动时,报错:Failed to create the Java Virtual Machine。

    2.当导出的dump文件的大小大于配置的1024m(说明1中,提到的配置:-vmargs– Xmx1024m),MAT输出分析报告的时候,会报错:An internal error occurred during: "Parsing heap dump from XXX”。适当调大说明1中的参数即可。

    mat(Eclipse Memory Analysis Tools)导入 dump 文件进行分析

     

    内存泄漏问题一般直接选 Leak Suspects 即可,mat 给出了内存泄漏的建议。线程相关的问题可以选择 thread overview 进行分析。

     

    IBM 内存分析工具 ha457.jar

    IBM HeapAnalyzer 下载地址https://www.ibm.com/support/pages/ibm-heapanalyzer

     

     

     

     

    生产环境自动dump出oom信息的phrof文件

    使用启动参数 -XX:+HeapDumpOnOutOfMemoryError

    导出地址:-XX:+HeapDumpPath={path}

    2. GC 问题和线程

    GC 问题除了影响 CPU 也会影响内存,排查思路也是一致的。一般先使用 jstat 来查看分代变化情况,比如 youngGC 或者 fullGC 次数是不是太多;EU、OU 等指标增长是不是异常等。

    线程的话太多而且不被及时 gc 也会引发 oom,大部分是unable to create new native thread。

    3. 堆外内存

    堆外内存溢出表现就是物理常驻内存增长快,报错的话视使用方式都不确定,如果由于使用 Netty 导致的,那错误日志里可能会出现OutOfDirectMemoryError错误,如果直接是 DirectByteBuffer,那会报OutOfMemoryError: Direct buffer memory。

    堆外内存溢出往往是和 NIO 的使用相关,一般我们先通过 pmap 来查看下进程占用的内存情况pmap -x pid | sort -rn -k3 | head -30,意思是查看对应 pid 倒序前 30 大的内存段。这边可以再一段时间后再跑一次命令看看内存增长情况,或者和正常机器比较可疑的内存段在哪里。

     

    关键还是要看错误日志栈,找到可疑的对象,搞清楚回收机制,然后去分析对应的对象。比如 DirectByteBuffer 分配内存的话,是需要 full GC 或者手动 system.gc 来进行回收的。那么其实我们可以跟踪一下 DirectByteBuffer 对象的内存情况,通过jmap -histo:live pid手动触发 fullGC 来看看堆外内存有没有被回收。如果被回收了,那么大概率是堆外内存本身分配的太小了,通过-XX:MaxDirectMemorySize进行调整。如果没有什么变化,那就要使用 jmap 去分析那些不能被 gc 的对象,以及和 DirectByteBuffer 之间的引用关系了。

    五、GC 问题

    堆内内存泄漏总是和 GC 异常相伴。不过 GC 问题不只是和内存问题相关,还有可能引起 CPU 负载、网络问题等,只是相对来说和内存联系紧密些。

    在启动参数中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps来开启 GC 日志。GC 日志能大致推断出 youngGC 与 fullGC 是否过于频繁或者耗时过长

    1. youngGC 过频繁

    youngGC 频繁一般是短周期小对象较多,先考虑是不是新生代设置的太小了,看能否通过调整-Xmn、-XX:SurvivorRatio 等参数设置来解决问题。如果参数正常,但是 young gc 频率还是太高,就需要使用 Jmap 和 MAT 对 dump 文件进行进一步排查了。

    -Xmn2G   设置年轻代大小为2G

    -XX:SurvivorRatio=8   定义了新生代中Eden区域和Survivor区域的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10

    2. youngGC 耗时过长

    耗时过长问题就要看 GC 日志里耗时耗在哪一块了。

    3. 触发 fullGC

    (1)直接调用 System.gc() 时(调用后并不会立即发生 fullGC,后面会在某个时间点发生)

    (2)老年代的可用空间不足时

    (3)方法区空间不足时,或 Metaspace Space 使用达到 MetaspaceSize 但未达到 MaxMetaspaceSize 阈值;大多情况下扩容都会触发

    (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存时。由 Eden 区、From Survior 区向 To Survior 区复制时,对象大小大于 To Survior 区可用内存,则把该对象转存到老年代

    (5)执行 jmap -histo:live 或者 jmap -dump:live;

    分析过程

    (1)找到内存使用率最高的进程

    (2)利用jmap生成该进程的堆转储快照

    (3)利用转储快照分析工具分析问题

    六、总结

    1. JVM 常用命令

    jps:列出正在运行的虚拟机进程

    jstat:监视虚拟机各种运行状态信息,可以显示虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据

    jinfo:实时查看和调整虚拟机各项参数

    jmap:生成堆转储快照,也可以查询 finalize 执行队列、Java 堆和永久代的详细信息

    jstack:生成虚拟机当前时刻的线程快照

    jhat:虚拟机堆转储快照分析工具

    jmap 搭配使用,分析 jmap 生成的堆转储快照,与 MAT 的作用类似

    2. 排查步骤

    1、先找到对应的进程: PID

    2、生成线程快照 stack (或堆转储快照: hprof )

    3、分析快照(或堆转储快照),定位问题

    3. 内存泄露、内存溢出和 CPU 100% 关系

     

    4. 常用 JVM 性能检测工具

    Eclipse Memory Analyer、JProfile、JProbe Profiler、JVisualVM、JConsole、Plumbr





    没有高深的知识,没有进阶的技巧,万丈高楼平地起~!

  • 相关阅读:
    过滤器--起步阶段
    常用指令-起步阶段
    模型和控制器-起步阶段
    指令介绍-起步阶段
    AngularJS简介-起步阶段
    c++之helloworld与命名空间
    c++ 之重要性
    一个没曾摸透的程序
    linux中什么是文件结构体?
    很全的linux网络编程技巧
  • 原文地址:https://www.cnblogs.com/aaron911/p/15206320.html
Copyright © 2011-2022 走看看