在实际开发过程中,有些 Java 程序在本地或者在服务器上都可以运行的较正常,但是运行较长一段时间后,可能会出现资源占用率较高的情况,例如 CPU 或 内存占用率较高等情况,以至于发生内存溢出,进程假死等的情况。这些问题发生的原因,往往是那些易忽略的编程规范导致的。下面描述一个定位开发环境上资源占用率较高问题的流程。
1. top 命令查看资源占用率进程
获得占用资源较高的进程 pid 后,可以通过 ps -ef | grep pid 查看该进程属于哪一个服务,方便后续查看具体代码,搜索问题。
2.查看该进程下哪些线程资源占用率较高
使用 top -Hp pid 可以查看该进程下各线程的资源占用情况,找到资源占用较高的线程 pid,此处以 t_pid 表示该值。此处需要切换为执行 pid 进程的用户来查看进程信息。
后续使用的命令可能需要自己下载对应版本的 jdk,一般环境上只提供服务运行的 jre。这里简单的方式为下载服务对应的 java 版本的 jdk,例如放在 /tmp 目录下,配置 ~/.bashrc 文件,添加如下内容,并执行 source .bashrc。
JAVA_HOME=/tmp/jdk/
JRE_HOME=$JAVA_HOME/jre
JAVA_BIN=$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
export JAVA_HOME JRE_HOME PATH CLASSPATH
export JAVA_OPTS=-Djava.awt.headless=true
3.查看 pid 进程的堆栈信息
使用 jstack pid 查看该进程下各线程的堆栈信息,搜索线程中 nid 值为上一步获得的 t_pid 线程。如果是一般用户线程,则可以根据线程的调用链,在服务代码中定位出具体故障位置。
4.查看内存使用情况
使用 jmap -heap pid 查看内存中各区域使用情况,使用 jmap -histo pid 查看内存中的存储对象类型。如果上一步堆栈信息中占用资源较高的线程为 GC 线程的话,则该步骤查看的内存使用情况可以辅助判断 GC 的性能优略。
如果是 CPU 使用率过高,一般是该 t_pid 为一个死循环;如果是内存使用率过高,则该 t_pid 可能为 GC 线程或构造对象线程。
下面提供一种曾经遇到过的场景供参考:
使用 top 命令查看资源使用情况时发现,CPU 使用率一直较高,内存使用率忽高忽低。在具体进程中发现有两个线程的 CPU 使用率较高,在进程堆栈中查看这两个线程一个为 VM Thread 协调线程,一个为 GC 线程。查看内存使用,发现偶尔内存各区域会出现占满情况,且存储大量 Map 的 Entry 对象,因为关联关系为强引用,导致 GC 效率较低且频繁执行,进程空间得不到释放,直到线程执行完毕。