线上OOM排查

简介: 本文介绍了JDK工具的使用方法及其应用场景。首先详细说明了`jps`、`jstack`、`jstat`和`jmap`等工具的基本用法及参数含义,帮助开发者实时监控Java进程的状态,诊断线程问题及内存使用情况。接着介绍了`jvisualvm.exe`和`MemoryAnalyzer.exe`两款内存诊断工具,展示了如何通过这些工具进行内存分析。最后,文章提供了在线上OOM故障排查的具体步骤,并给出了解决方案示例,以便开发者更好地理解和解决实际问题。

JDK工具

jps

查看服务器中当前用户下的Java进程

bash

代码解读

复制代码

usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]

常用参数解释:
-q : 显示Java进程的进程ID,不显示主类名称、JAR文件名和传递给主方法的参数
-m : 显示Java虚拟机启动时传递给main()方法的参数
-l : 显示主类的完整包名,如果进程执行的是JAR文件,也会显示JAR文件的完整路径
-v : 显示Java虚拟机启动时传递JVM的参数

jstack

查看Java虚拟机在当前时刻的线程快照。

线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

bash

代码解读

复制代码

Usage:
    jstack [-l] <pid>
        (连接到正在运行的进程)
    jstack -F [-m] [-l] <pid>
        (连接到挂起的进程)
    jstack [-m] [-l] <executable> <core>
        (连接到核心文件)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (连接到远程调试服务器)

Options:
    -F  强制线程转储。当 jstack <pid> 没有响应(进程挂起)时使用
    -m  打印java和native c/c++框架的所有栈信息(混合模式)
    -l  长列表。打印有关锁定的其他信息,如属于java.util.concurrent的ownable synchronizers列表.
    -h 查看帮助信息
    -help 同 -h

jstat

Java虚拟机统计信息工具,利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控

bash

代码解读

复制代码

Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      jstat -options 得到的选项值
  <vmid>        虚拟机标识符
  <lines>       标题行之间的样本数
  <interval>    采样间隔。允许以下形式:<n>["ms"|"s"] 其中 <n> 是整数,后缀指定单位为 毫秒(“ms”)或秒(“s”)。默认单位为“ms“
  <count>       终止前要进行的采样数
  -J<flag>      将 <flag> 直接传递给运行时系统

jstat -options 选项
-class : 监视类装载、卸载数量、总空间及类装载所耗费的时间
-compiler : 输出JIT编译器编译过的方法、耗时等信息
-gc : 监视 Java堆状况,包括Eden区、2个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息
-gccapacity : 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大和最小空间
-gccause : 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因
-gcmetacapacity : 显示关于metaspsce大小的统计信息
-gcnew : 监视新生代 GC的状况
-gcnewcapacity : 监视内容与-gcnew基本相同,输出主要关注使用到的最大和最小空间
-gcold : 监视老年代GC的状况
-gcoldcapacity : 监视内容与-gcold基本相同,输出主要关注使用到的最大和最小空间
-gcutil : 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-printcompilation : 当前VM的执行信息

使用示例

jstat -gc <pid>  查看内存及GC情况

bash

代码解读

复制代码

C:\Users\Administrator>jstat -gc 6400
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
75776.0 81408.0 67606.5  0.0   536064.0 105037.9 1398272.0   602334.0  245488.0 236296.6 24616.0 23082.8    132    2.093   5      0.992    3.084

参数解释:
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

jstat -gccause <pid> 查看导致GC的原因

bash

代码解读

复制代码

C:\Users\Administrator>jstat -gccause 6400
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
 89.22   0.00  22.96  43.08  96.26  93.77    132    2.093     5    0.992    3.084 Allocation Failure   No GC

参数解释:
LGCC: 上一次GC的原因
GCC: 当前GC的原因

jmap

JDK提供的用来监视进程运行中JAVA物理内存占用情况的工具,用于生成堆转储快照,执行该指令时会影响线上服务的运行

bash

代码解读

复制代码

Usage:
    jmap [option] <pid>
        (用于连接到正在运行的进程)
    jmap [option] <executable <core>
        (用于连接到核心文件)
    jmap [option] [server_id@]<remote server IP or hostname>
        (用于连接到远程调试服务器)

where <option> is one of:
    <none>               查看进程的内存映像 类似于Solaris pmap命令
    -heap                打印 Java 堆摘要
    -histo[:live]        打印 java 对象堆的直方图;如果指定了“live”子选项,则仅计数活动对象
    -clstats             打印类加载器统计信息
    -finalizerinfo       打印等待完成的对象的信息
    -dump:<dump-options> 以 hprof 二进制格式转储 java 堆
                         dump-options:
                           live         仅转储活动对象;如果未指定,则转储堆中的所有对象。
                           format=b     二进制格式
                           file=<file>  将堆转储到 <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   与 -dump:<dump-options> <pid> 或 -histo 一起使用在 <pid> 没有响应时强制进行堆转储或直方图。此模式不支持“live”子选项
    -h | -help           打印此帮助消息
    -J<flag>             将 <flag> 直接传递给运行时系统

使用示例:

jmap -dump:format=b,file=heapdump.hprof <pid>生成进程的内存快照存储在heapdump.hprof文件中。

内存诊断工具

jvisualvm.exe

JDK提供的JVM监控工具,一般位于%JAVA_HOME%\bin目录下, 可以通过这个工具对Java进程进行实时监控或者对转储出来的堆栈文件进行分析。

MemoryAnalyzer.exe

Eclipse 提供的内存分析软件(下载地址), 可以生成饼图,看起来更加直观。

线上OOM排查

1、应用启动时增加JVM参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file>, <file> 可以是指定的文件或者目录,指定为目录时转储的文件是存储在该目录下,文件名由系统随机生成。

例如: java -Xms1024m -Xmx2048m  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dump -jar  test.jar

2、如果没有配置JVM参数在OOM时生成快照 使用jps -ml命令找到对应Java进程的 <pid>使用jmap -dump:format=b,file=heapdump.hprof <pid> 生成内存快照

3、使用内存分析工具打开生成的快照文件,对其进行分析,使用 jvisualvm 打开文件,可以看到导致OOM的线程,点进去查看其堆栈。

bash

代码解读

复制代码

"http-nio-39805-exec-4" daemon prio=5 tid=102 RUNNABLE
	at java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:48)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:757)
	   Local Variable: java.security.ProtectionDomain#30
	at java.lang.ClassLoader.defineClass(ClassLoader.java:636)
	at sun.reflect.GeneratedMethodAccessor209.invoke(<unknown string>)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.sun.xml.bind.v2.runtime.reflect.opt.Injector.inject(Injector.java:125)
	   Local Variable: java.lang.String#790762
	   Local Variable: com.sun.xml.bind.v2.runtime.reflect.opt.Injector#1
	at com.sun.xml.bind.v2.runtime.reflect.opt.Injector.inject(Injector.java:48)
	at com.sun.xml.bind.v2.runtime.reflect.opt.AccessorInjector.prepare(AccessorInjector.java:51)
	at com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedAccessorFactory.get(OptimizedAccessorFactory.java:128)
	   Local Variable: java.lang.reflect.Field#11880
	at com.sun.xml.bind.v2.runtime.reflect.Accessor$FieldReflection.optimize(Accessor.java:204)
	   Local Variable: com.sun.xml.bind.v2.runtime.reflect.Accessor$FieldReflection#5
	at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.<init>(SingleElementLeafProperty.java:45)
	   Local Variable: com.sun.xml.bind.v2.model.impl.RuntimeTypeRefImpl#5
	   Local Variable: com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty#5
	at sun.reflect.GeneratedConstructorAccessor140.newInstance(<unknown string>)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.sun.xml.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:88)
	at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:135)
	   Local Variable: com.sun.xml.bind.v2.model.impl.RuntimeElementPropertyInfoImpl#5
	   Local Variable: com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl#3
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:448)
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:282)
	   Local Variable: java.util.Collections$EmptyList#1
	   Local Variable: com.sun.xml.bind.v2.model.impl.RuntimeTypeInfoSetImpl#2
	   Local Variable: com.sun.xml.bind.v2.runtime.JAXBContextImpl#2
	   Local Variable: com.sun.xml.bind.v2.model.impl.RuntimeClassInfoImpl#3
	at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:84)
	at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:66)
	at sun.reflect.GeneratedMethodAccessor3352.invoke(<unknown string>)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:247)
	at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:234)
	at javax.xml.bind.ContextFinder.find(ContextFinder.java:441)
	   Local Variable: java.io.BufferedReader#1
	at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:641)
	at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:584)

4、根据堆栈和内存分析,可能是由于JAXBContext.newInstance 方法短时间被频繁调用导致占用大量内存且未被释放(该方法在定时任务中被调用)

5、JAXBContext 是可以复用的,那么可以考虑持有一个对象池,优先从对象池中加载 JAXBContext ,池中取不到对象时再 new 一个对象,然后放入对象池中。此方法在JAXBContext 类型较少时可以适用,如果JAXBContext 类型很多的情况下,对对象池的处理逻辑需要重新编写,不能一直往里添加对象,可以考虑在对象池的大小超过一定阈值时使用先进先出或最近最少算法淘汰之前添加的对象。

java

代码解读

复制代码


private static ConcurrentHashMap<String, JAXBContext> JAXBCONTEXT_MAP = new ConcurrentHashMap<>(32);


private static JAXBContext instanceJAXBContext(Class<?> cls) throws JAXBException {
		JAXBContext jc = JAXBCONTEXT_MAP.get(cls.getName());
		if (Objects.isNull(jc)) {
			jc = JAXBContext.newInstance(cls);
			JAXBCONTEXT_MAP.put(cls.getName(), jc);
		}
		return jc;
	}

转载来源:https://juejin.cn/post/7385245216053542946

相关文章
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
217 0
|
Arthas Java 测试技术
【线上问题排查】死锁和僵尸进程排查
【线上问题排查】死锁和僵尸进程排查
207 1
|
Arthas 测试技术
Arthas排查生产环境CPU飚高问题
Arthas排查生产环境CPU飚高问题
167 0
Arthas排查生产环境CPU飚高问题
|
Arthas 监控 Java
Arthas 实践——生产环境排查 CPU 飚高问题
13:40 收到我们的生产环境服务器绿版 CUP 超负载告警通知。此时心里只有一个想法,重启大法好,马上登录服务器,执行 top 发现进程 30247 和 28337 占用 CPU 为 200 多和100 多基本占用了 4 核的 3 核,整个过程大概用时 30 秒,维护群依然很平静,运营的电话也没打过来,这时候我断定,这次问题应该影响面很小,用户可能也暂时没有发现,好吧,还有时间做排查。
Arthas 实践——生产环境排查 CPU 飚高问题
|
4月前
|
消息中间件 Java 调度
一次线上服务CPU100%的排查过程
文章记录了一次线上服务CPU使用率达到100%的排查过程,通过使用top命令和jstack工具确定了导致高CPU使用的线程,并分析了Disruptor组件的不当配置是问题原因,通过修改组件的策略成功解决了问题。
91 0
|
7月前
|
SQL 监控 数据库
线上服务假死排查
线上服务假死排查
54 0
|
监控 NoSQL Java
【线上问题】服务CPU彪高排查
后端程序员出去面试经常会有面试官喜欢问你有没有排查过线上问题,遇到后怎么排查的。
545 0
【线上问题】服务CPU彪高排查
|
消息中间件 存储 缓存
【10个OOM异常的场景以及对应的排查经验】
【10个OOM异常的场景以及对应的排查经验】
219 0
|
监控 Java
【线上问题排查】CPU100%和内存100%排查
【线上问题排查】CPU100%和内存100%排查
182 1