线上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开发
jvisualvm分析jvm内存溢出
jvisualvm分析jvm内存溢出
2132 0
jvisualvm分析jvm内存溢出
|
安全 前端开发 JavaScript
Java精品视频全套资源汇总,自学Java人手一套!
发现有很多粉丝是学java的,为了大家能够快速成长,所以我今天精心挑选了一些java相关的视频资源分享给大家,大家一定好好利用起来,这些技术学会之后,进大厂指日可待,加油。
9059 0
|
机器学习/深度学习 人工智能 前端开发
终端里的 AI 编程助手:OpenCode 使用指南
OpenCode 是开源的终端 AI 编码助手,支持 Claude、GPT-4 等模型,可在命令行完成代码编写、Bug 修复、项目重构。提供原生终端界面和上下文感知能力,适合全栈开发者和终端用户使用。
61687 11
|
人工智能 负载均衡 应用服务中间件
Dify 性能瓶颈?Higress AI 网关为它注入「高可用之魂」!
Dify 是一款开源 AI 应用开发平台,因其灵活的工作流编排和易用性受到广泛关注,但在用户规模扩大和生产落地过程中,逐渐暴露出性能瓶颈,影响系统稳定性。本文介绍如何通过 Higress AI 网关提升 Dify 应用的全链路高可用性,并提供详细操作指南。AI 网关具备多维度限流、Token 级控制、模型 Fallback、负载均衡等能力,有效保障 Dify 应用在高并发场景下的稳定运行。
1511 1
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
12064 139
|
监控 网络协议 NoSQL
java线上排查OOM内存溢出
java线上排查OOM内存溢出
1258 1
|
XML JSON Java
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
|
存储 Prometheus 监控
性能监控之初识 Prometheus
【8月更文挑战第2天】性能监控之初识 Prometheus
2486 18