jvm 分析调优工具
jps命令查看java进程pid
jps 列出java进程名(不完整类名) 和pid
jps -l 列出java进程名(完整类名) 和pid
jmap命令查看java进程占用的jvm资源情况
jmap -histo pid 查看内存情况
jmap -heap pid 查看java程序的堆占用信息
jmap -dump 导出heap快照分析文件heap.hprof
生成的heap.hprof文件可以使用mat工具或jvisualvm工具 进行分析。
可以在jar启动时配置 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./(指定路径)
这样就能设置内存溢出自动导出dump文件到指定的路径
mat工具是第三方厂商提供的工具,
一般使用jdk自带的jvisualvm 工具查看导出的heap.hprof文件
控制台输入jvisualvm命令即可打开jdk自带的可视化jvm分析工具
点文件->装入 ,把heap.hprof文件导入到visualVM里,就可以看到堆dump的详细信息
jinfo命令查看java程序的jvm参数和sys配置参数
jinfo查看java程序的jvm参数
jinfo -flags pid 可以看到程序的Xmx Xms等参数配置情况
jinfo查看java程序的系统参数
查看java系统参数,使用命令 jinfo -sysprops pid
jstat命令查看堆内存各部分的使用量已经加载类的数量
gc垃圾回收统计 jstat -gc pid
堆内存统计 jstat -gccapacity pid
新生代垃圾回收统计 jstat -gcnew pid
新生代内存统计 jstat -gcnewcapacity pid
老年代垃圾回收统计 jstat -gcold pid
老年代内存统计 jstat -gcoldcapacity pid
元数据空间统计 jstat -gcmetacapacity pid
垃圾回收简化 jstat -gcutil pid
场景1-分析线上应用OOM后生成的dump文件
线上应用的jvm启动参数配置
-Xms28m -Xmx56m -XX:+HeapDumpOnOutOfMemoryError
代码案例OOMTest.java
package cn.demo;
import java.util.ArrayList;
import java.util.List;
public class OOMTest {
public static void main(String[] args) {
List<ThirdUser> list = new ArrayList<>();
while (true){ //while true 死循环,会使程序一直执行
ThirdUser thirdUser = new ThirdUser();
thirdUser.setUsername("1");
thirdUser.setAge(1);
thirdUser.setHobby("ctrl");
list.add(thirdUser); //由于配置了-Xmx56m,当程序占用的jvm内存大于56m后会报OOM
}
}
}
class ThirdUser {
private String username;
private Integer age;
private String hobby;
public ThirdUser() {
}
public ThirdUser(String username, Integer age, String hobby) {
this.username = username;
this.age = age;
this.hobby = hobby;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
@Override
public String toString() {
return "ThirdUser{" +
"username='" + username + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
启动运行后,会报错OutOfMemoryError
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid5768.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at cn.demo.OOMTest.main(OOMTest.java:18)
Heap dump file created [106019329 bytes in 0.408 secs]
在这个java程序根目录下会生成 dump文件 java_pid5768.hprof ,然后,使用jvisualvm 或 jprofiler 工具进行分析hprof文件。
这里以jvisualvm工具为例,把java_pid5768.hprof导入到jvisualvm后,可以看到如下界面
场景2-利用jstack命令排查程序死锁问题
代码案例MustDeadLockDemo.java
package cn.demo;
public class MustDeadLockDemo {
static class DeadLockTask implements Runnable{
private boolean flag;
private Object lock1;
private Object lock2;
public DeadLockTask(boolean flag, Object lock1, Object lock2) {
this.flag = flag;
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
if (flag){
synchronized (lock1){
System.out.println(Thread.currentThread().getName()+"->拿到锁1");
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"->等待锁2释放...");
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"->拿到锁2");
}
}
}
if (!flag){
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"->拿到锁2");
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"->等待锁1释放...");
synchronized (lock1){
System.out.println(Thread.currentThread().getName()+"->拿到锁1");
}
}
}
}
}
public static void main(String[] args){
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadLockTask(true,lock1,lock2),"线程1").start();
new Thread(new DeadLockTask(false,lock1,lock2),"线程2").start();
}
}
这个程序执行会因为拿不到对方的锁而一直阻塞,无法结束。
首先使用 jps命令找到这个java进程的pid
jps -l
然后使用jstack 命令查看这个java进程的线程dump信息
jstack pid
解决死锁的办法:打破多线程对资源的死锁占用,使其存在先后执行、先后占用的顺序。
场景3-使用jstack找出占用cpu最高的线程堆栈信息
代码案例CpuMaxTest.java
package cn.demo;
public class CpuMaxTest {
public static final int initData = 66;
public static UserTest user = new UserTest();
private UserTest user2 = new UserTest();
public int compute(){
int a =1;
int b =2;
int c = (a+b)*10;
return c;
}
public static void main(String[] args) {
CpuMaxTest test = new CpuMaxTest();
while (true){
test.compute();
}
}
}
class UserTest {
private String uid;
private String name;
UserTest() {
}
UserTest(String uid, String name) {
this.uid = uid;
this.name = name;
}
}
这个程序执行后会导致CPU占用 99%,排查步骤如下
1用 jps -l 列出java进程的进程号 和 进程名
jps -l
2使用命令 top -p pid ,查看这个java进程在linux的内存情况,pid是你的java进程号
top -p 8318
3按大写的H键 ,获取每个线程的内存情况
4找到内存和CPU占用最高的线程tid ,比如1804
5将1804转换成16进制,使用命令
printf '%x\n' 1804
6执行 如下的jstack 命令,得到线程堆栈信息中 0x2119这个线程所在行后面10行的信息。
jstack 进程pid | grep -A 10 线程id的16进制
7根据上面的堆栈信息找出可能存在问题的代码。