jvm监控及问题排查

jvm监控及问题排查

文章目录

  • jvm监控及问题排查
      • 一、简介
      • 二、知识点
        • 2.1 jvm内存区域
        • 2.2 常用垃圾回收器
        • 2.3 监控的内容
      • 三、常用工具
        • 3.1 jps
        • 3.2 jinfo
        • 3.3 jstat
        • 3.4 jmap
        • 3.5 jhat
        • 3.6 jstack
      • 四、问题排查完整示例
        • 4.1 获取占cpu过高的应用程序pid
        • 4.2 查询应用程序内部占cpu过高的线程id
        • 4.3 将占cpu过高的线程id转为16进制
        • 4.4 在jvm中查看占cpu过高的堆栈信息
      • 五、备注
        • 5.1 jinfo或jmap运行异常

一、简介

这里对运行中的java虚拟机jvm(hotspot虚拟机,其上运行着java程序)监控及问题排查进行介绍。

二、知识点

2.1 jvm内存区域

jvm内存划分为几个不同的区域,如下:

  1. 程序计数器:线程私有(每个线程都有独立的程序计数器),记录当前线程执行的字节码指令。
  2. 虚拟机栈(本地方法栈类似):线程私有,方法执行时会创建栈帧,方法的执行过程对应着栈帧在虚拟机栈中从入栈到出栈的过程。
  3. java堆:所有线程共享,存放对象实例和数组。通常分为新生代(包含eden区、from service区、to service区)和老年代。堆的大小可以通过-Xms和-Xmx控制。
  4. 方法区:所有线程共享,用于存储jvm加载的类信息、常量、静态变量等数据。按分代划分,可称为永久代,使用-XX:MaxPermSize设置上限。

2.2 常用垃圾回收器

java中内存是动态分配的,对用就有垃圾收集,采用的算法有标记-清除(mark-sweep)算法、复制(copying)算法、标记-整理(mark-compact)算法。垃圾收集针对的是java堆和方法区(程序计数器、虚拟机栈、本地方法栈为线程私有,随着线程生和灭)。常用垃圾收集器有:

  1. Serial收集器:单线程收集器,垃圾收集时会暂停(stop the world)其他所有工作线程,直到收集结束。简单高效,用于新生代收集。
  2. ParNew收集器:serial收集器的多线程版本。用于新生代收集。
  3. Parallel Scavenge收集器:采用复制算法、并行的、吞吐量优先的多线程收集器。用于新生代收集。
  4. Serial old收集器:采用标记-整理算法的单线程收集器。serial收集器的老年代版本。用于老年代收集。
  5. Parallel old收集器:采用标记-整理算法的多线程收集器。parallel scavenge收集器的老年代版本。用于老年代收集
  6. cms(concurrent mark sweep)收集器:采用标记-清除算法、最短停顿时间的多线程收集器。
  7. G1收集器:整体采用标记-整理算法、基于region(整个java堆划分为多个大小相等的独立区域)管理整个堆(不需要其他收集器配合)、降低和可预测停顿的多线程收集器。

2.3 监控的内容

监控的内容有:

  1. jvm配置信息
  2. jvm内存使用
  3. jvm线程信息

三、常用工具

3.1 jps

查看jvm进程。

格式:jps [option]
option:
-l:显示运行类的完整包
-q:只显示进程号
-m:显示给main方法的参数
-v:显示给jvm的完整参数

示例:

输入:jps -l
输出:
17419 com.dragon.study.msa_register_center.MsaRegisterCenterApplication

3.2 jinfo

查看虚拟机配置信息。

格式:jinfo vmid

3.3 jstat

查看堆内存使用情况,类加载的数量等。

格式:jstat [ -option vmid [ interval [s|ms] [count] ] ]
option:
-options:查看可用参数
-class:类加载统计
-compiler
-gc:查看堆内存各区域使用情况
-gccapacity
-gccause:堆内存各区域使用占比,以及最后一次和当前(当前可能没有)引发垃圾回收的诱因
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil:查看堆内存各区域使用占比
-printcompilation:编译情况

示例1,查看堆内存各区域使用情况:

输入: jstat -gc 17419
输出:
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
22528.0 20480.0  0.0   20287.0 794624.0 386301.9  96768.0    39076.0   73304.0 70454.7 9600.0 9037.9     11    1.539   3      0.320    1.859

解释:

容量的单位是KB
S0C: service0区容量
S1C: service1区容量
S0C: service0区使用量
S1C: service1区使用量
EC: eden区容量
EU: eden区使用量
OC: 老年代容量
OU: 老年代使用量
PC: 永久代容量
PUMU: 永乐代使用量
MC: 方法区容量
MU: 方法区使用量
CCSC: 压缩类空间容量
CCSU: 压缩类空间使用量
YGC: minor gc的次数
YGCT: minor gc耗时,单位秒
FGC: full gc的次数
FGCT: full gc耗时,单位秒
GCT: 所有gc耗时,单位秒

示例2,查看堆内存各区域使用占比:

输入: jstat -gcutil 17419
输出:
S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.06  48.83  40.38  96.11  94.15     11    1.539     3    0.320    1.859

解释:

S0: service0使用比例
S1: service1使用比例
EC: eden区使用比例
O: 老年代使用比例
M: 方法区使用比例
CCS: 压缩类空间使用量
YGC: minor gc的次数
YGCT: minor gc耗时,单位秒
FGC: full gc的次数
FGCT: full gc耗时,单位秒
GCT: 所有gc耗时,单位秒

示例3,类加载统计:

输入: jstat -class 17419
输出:
Loaded  Bytes  Unloaded  Bytes     Time   
 13604 27320.2        1     1.1      39.70

示例4,堆内存各区域使用占比,以及最后一次和当前(当前可能没有)引发垃圾回收的诱因:

输入: jstat -gccause 17419
输出:
 S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC                 
  0.00  99.06  51.05  40.38  96.11  94.15     11    1.539     3    0.320    1.859 Allocation Failure   No GC 

3.4 jmap

生成堆转储快照,查看堆详细信息等。

格式:jmap [option] vmid
option:
-heap: 显示堆详细信息,使用的哪种回收器、分代使用情况
-histo: 显示堆中对象统计信息
-finalizerinfo: 在F-Queue中等待Finalizer线程执行finalize方法的对象
-dump: 生成堆转储快照(配合jhat使用),格式为: -dump:[live,]format=b,file=  live子参数指明是否只出存活的参数

示例1,显示堆详细信息,使用的哪种回收器、分代使用情况:

输入: jmap  -heap 17419
输出:
Attaching to process ID 17419, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2573205504 (2454.0MB)
   NewSize                  = 53477376 (51.0MB)
   MaxNewSize               = 857735168 (818.0MB)
   OldSize                  = 108003328 (103.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 813694976 (776.0MB)
   used     = 417139520 (397.81524658203125MB)
   free     = 396555456 (378.18475341796875MB)
   51.26485136366382% used
From Space:
   capacity = 20971520 (20.0MB)
   used     = 20773896 (19.81153106689453MB)
   free     = 197624 (0.18846893310546875MB)
   99.05765533447266% used
To Space:
   capacity = 23068672 (22.0MB)
   used     = 0 (0.0MB)
   free     = 23068672 (22.0MB)
   0.0% used
PS Old Generation
   capacity = 99090432 (94.5MB)
   used     = 40013792 (38.160125732421875MB)
   free     = 59076640 (56.339874267578125MB)
   40.38108543113426% used

44501 interned Strings occupying 5591688 bytes.

示例2,生成堆转储快照:

输入: jmap -dump:format=b,file=17419.txt  17419
结果:新生成文件17419.txt文件,解析配合jhat使用

3.5 jhat

jvm转储快照分析工具。

格式:jhat filename

示例,使用前面生成的dump文件17419.txt:

jhat 17419.txt

运行后在浏览器中使用地址: http://localhost:7000 查看

3.6 jstack

查看当前时刻每一个线程正在执行的方法堆栈信息。

格式:jstack [option] vmid
option:
-F: 当正常输出请求不被相应时,强制输出
-l: 除堆栈外,还输出锁信息

示例:

输入: jstack  -l 17419
部分输出:
"http-nio-8010-ClientPoller-1" #114 daemon prio=5 os_prio=0 tid=0x00007f421354a800 nid=0x44e9 runnable [0x00007f41409d4000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
        at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
        at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
        - locked <0x00000007a137e200> (a sun.nio.ch.Util$3)
        - locked <0x00000007a137e1f0> (a java.util.Collections$UnmodifiableSet)
        - locked <0x00000007a137e0d8> (a sun.nio.ch.EPollSelectorImpl)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
        at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:754)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

解析:
nid为16进制的线程id。

四、问题排查完整示例

这里以排查应用程序cpu使用率过高为例,排查步骤如下:

4.1 获取占cpu过高的应用程序pid

输入: top
输出:
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                  20959 china     20   0 8345840 635048  25048 S   4.6  6.3   0:24.87 java         

由上输出确定,占cpu过高pid为20959

4.2 查询应用程序内部占cpu过高的线程id

输入: top -Hp 20959
部分输出:
 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 20960 china     20   0 8345840 636332  25048 S  0.7  6.3   0:17.92 java
 21060 china     20   0 8345840 636332  25048 S  0.3  6.3   0:01.55 java  

由上输出确定,占cpu过高线程id为20960

4.3 将占cpu过高的线程id转为16进制

输入: printf '%x\n' 20960
输出:51e0

20960的16进制为51e0

4.4 在jvm中查看占cpu过高的堆栈信息

输入: jstack 20959 | grep -i -A10 51e0
输出:
"main" #1 prio=5 os_prio=0 tid=0x00007f7040018800 nid=0x51e0 waiting on condition [0x00007f7049190000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.dragon.study.msa_client.MsaClientApplication.main(MsaClientApplication.java:26)

由此可发现MsaClientApplication类的26行可能有问题。

五、备注

5.1 jinfo或jmap运行异常

现象:

Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 19948: Operation not permitted

解决:
方式一(临时修改):

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

方式二(永久修改):

vim /etc/sysctl.d/10-ptrace.conf
将 kernel.yama.ptrace_scope = 1 改为: kernel.yama.ptrace_scope =0

你可能感兴趣的:(jvm)