线上OOM排查

JDK工具

jps

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

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

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应用程序的资源和性能进行实时的命令行的监控

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情况

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的原因

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物理内存占用情况的工具,用于生成堆转储快照,执行该指令时会影响线上服务的运行

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>生成<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的线程,点进去查看其堆栈。

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


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;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容