线上OOM排查

2024-09-06 15:30:08 浏览数 (2)

JDK工具

jps

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

代码语言:javascript复制
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来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

代码语言:javascript复制
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应用程序的资源和性能进行实时的命令行的监控

代码语言:javascript复制
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情况

代码语言:javascript复制
bash 代码解读复制代码C:UsersAdministrator>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的原因

代码语言:javascript复制
bash 代码解读复制代码C:UsersAdministrator>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物理内存占用情况的工具,用于生成堆转储快照,执行该指令时会影响线上服务的运行

代码语言:javascript复制
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的线程,点进去查看其堆栈。

代码语言:javascript复制
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 类型很多的情况下,对对象池的处理逻辑需要重新编写,不能一直往里添加对象,可以考虑在对象池的大小超过一定阈值时使用先进先出或最近最少算法淘汰之前添加的对象。

代码语言:javascript复制
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;
	}

0 人点赞