Java程序在运行中经常遇到CPU或内存使用率高的问题,那么应该如何排查问题的原因呢,本文大概描述一下分析排查方法。
方式一、命令方式分析
1.排查占用CPU的进程
使用top命令,查找CPU和内存使用率最高的进程获取进程PID。(在大写打开的情况下按P键或者在大写没有打开的情况下按 shift+P键,会按照CPU使用率的高低进行排序,在大写打开的情况下按M键或者在大写没有打开的情况下按 shift+M键,会按照内存使用率的高低进行排序)
2.查找实际占用最高的线程
使用命令top -H -p PID,此处PID就是上一步获取的进程PID,通过此命令可以查看实际占用CPU最高的的线程的ID,此处会有几个TID。
3.获取对应线程的线程栈信息
使用命令printf "%x\n" tid,将线程ID转换为16进制
使用命令jstack PID(进程pid) |grep tid(线程pid16进制) -A 50,此处tid为上一步转换后的16进制,使用此命令可以查看到对应线程的线程栈信息,从对根据线程栈对对应的代码进行分析。
方式二、dump文件分析
1.dump文件输出
jmap -dump:live,format=b,file=20170307.dump 9729
file后面的是自定义的文件名,最后的数字是进程的pid
2.使用jvisualvm来分析dump文件:
jvisualvm是JDK自带的Java性能分析工具,在JDK的bin目录下,文件名就叫jvisualvm.exe。
jvisualvm可以监控本地、远程的java进程,实时查看进程的cpu、堆、线程等参数,对java进程生成dump文件,并对dump文件进行分析。
使用方式:直接双击打开jvisualvm.exe,点击文件->装入,在文件类型那一栏选择堆,选择要分析的dump文件,打开。
装入之后在界面右侧的概要、类等选项卡可以看到生成dump文件当时的堆信息
可以看到,dump文件里记录的堆中的实例,总大小大概300M左右,(用第一行的实例大小除以百分比就能算出来),和JVM默认的新生代的大小差不多,远小于JVM中设置的最大堆内存,也就是说,dump文件里记录的并不是实例大小达到最大堆内存时的状态。
为了验证一下,我自己在本地模拟了一下堆内存溢出的情形,并用jvisualvm监控.
分析:红框框出的部分是发生堆内存溢出时的情形,已使用的堆大小(蓝色部分)并没有增长特别明显,但是申请的堆的大小(黄色部分)从默认的400多兆急速上涨,涨到800M,然后内存溢出,然而使用的堆大小依然没怎么增长。
所以,dump文件中的实例列表其实是反映了使用的堆的情况,而使用的堆内存并没有达到预先设置的最大堆内存,只是在申请堆内存的过程中超出了预先设置的最大堆内存,然后内存溢出。
通过分析Dump文件就可以发现程序哪里有死锁。
OOM(Out Of Memory)
为什么会出现 OOM,一般由这些问题引起
分配过少:JVM 初始化内存小,业务使用了大量内存;或者不同 JVM 区域分配内存不合理
代码漏洞:某一个对象被频繁申请,不用了之后却没有被释放,导致内存耗尽
内存泄漏:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了。因为申请者不用了,而又不能被虚拟机分配给别人用
内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出
内存泄漏持续存在,最后一定会溢出,两者是因果关系
比较常见的 OOM 类型有以下几种:
java.lang.OutOfMemoryError: PermGen space
Java7 永久代(方法区)溢出,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。每当一个类初次加载的时候,元数据都会存放到永久代
一般出现于大量 Class 对象或者 JSP 页面,或者采用 CgLib 动态代理技术导致。
Java8 将永久代变更为元空间,报错:java.lang.OutOfMemoryError: Metadata space,元空间内存不足默认进行动态扩展。
我们可以通过 -XX:PermSize 和 -XX:MaxPermSize 修改方法区大小
java.lang.StackOverflowError
栈溢出,一般是由于程序中存在 死循环或者深度递归调用 造成的。如果栈大小设置过小也会出现溢出,可以通过 -Xss 设置栈的大小
虚拟机抛出栈溢出错误,可以在日志中定位到错误的类、方法。
java.lang.OutOfMemoryError: Java heap space
堆内存溢出,溢出的原因一般由于 JVM 堆内存设置不合理或者内存泄漏导致
如果是内存泄漏,可以通过工具查看泄漏对象到 GC Roots 的引用链。掌握了泄漏对象的类型信息以及 GC Roots 引用链信息,就可以精准地定位出泄漏代码的位置。
如果不存在内存泄漏,就是内存中的对象确实都还必须存活着,那就应该检查虚拟机的堆参数(-Xmx 与 -Xms),查看是否可以将虚拟机的内存调大些。
线上如遇到 JVM 内存溢出,可以分以下几步排查
1、jmap -heap PID 查看是否内存分配过小。
2、jmap -histo:live PID | more 查看是否有明显的对象分配过多且没有释放情况。
3、jmap -dump:file=./jvmdump.hprof PID导出 JVM 当前内存快照,使用 JDK 自带或 MAT 等工具分析快照。
如果上面还不能定位问题,那么需要排查应用是否在不断创建资源,比如网络连接或者线程,都可能会导致系统资源耗尽。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。