JDK中那些额外的工具

编写java我们一般只会用到javacjava这两个命令,最多还会使用javadoc这个,但是实际上JDK里面包含了非常多有用的工具,这里打算介绍几种。

命令行工具

这些工具由于是命令行中执行的,所以各个系统上都可以执行。

jps

JPS,即Java process status tool,能够显示系统内所有的虚拟机进程,直接输入就可以看到结果。

注意由于它本身自己也是用java实现的,所以会显示出它自己(如果整个系统只有它一个java虚拟机在执行的话)

image-20200105163353518

我的机器上执行了一个jar包,这个jar包的作用是生成一个随机数,然后对它进行因式分解,这里可以看到它的LVMID号是9547,而jps自身也有一个LVMID号是9636。而且由于是在同一台主机上,所以这个LVMID号和进程号其实是一样的。

最常用的就是jps -v,可以查看虚拟机启动的时候所带的参数了。

jstat

jstat,即JVM Statistic Monitoring Tool,用它可以显示出类的装载信息,垃圾收集信息等,在运行java程序的时候可以定位虚拟机性能问题。

接下来看看垃圾回收的性能查看:jstat -gc 9547 1000 3 这个的意思是查看垃圾收集情况,每1000毫秒一次,一共3次。

image-20200105163719385

可以清楚看到各个区域所使用的内存量。

jinfo

jinfo(Configuration Info for java),这个能够实时调整虚拟机的各项参数,但是仅仅限于一部分在运行期可写的数据。

jmap

生成堆转储快照(dump文件)也可以用来查看共享库等信息。

image-20200105165204968

PS:这是我第二次执行所以说文件已经存在了。

jhat

用来分析上面jmap生成的dump文件,但是由于这个分析过程一般很耗时,所以比较推荐在别的机器上执行。而且这个工具也比较简陋。

jstack

jstack(Stack Trace for java),可以生成所有线程的快照,这个快照一般称之为javacore文件,然后就可以进行分析了。

HSDIS

在介绍这东西前,首先先编译并且执行一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class Bar {
int a = 1;
static int b = 2;

public int sum(int c) {
return a + b + c;
}

public static void main(String[] args) {
new Bar().sum(5);
}
}

编译之后用下面的虚拟机参数去执行:java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,Bar.sum -XX:CompileCommand=compileonly,Bar.sum Bar,报错,这是因为我没有安装这个插件,然后我去网上找这个插件的官网,结果….

image-20200105171649174

可视化工具

上面介绍的这些工具都是在命令行中执行的,并不是非常直观,JDK还有两个很好用的可视化工具,分别是JHSDB、JConsole和VisualVM。

JHSDB

在这里开始前,需要先声明一下:变量,指针,对象(实例)这三者的关系。

Person p; 其中的p,称为一个变量。而这句话,就只是在内存中开辟了一小块空间

new Person();这句话会在堆里面创建一个实例,也被称作对象。

当上面的两句话合二为一,即Person p = new Person();,除了完成上面的两个,还同时让变量p的值,等于了对象在内存中的地址。

上来先有一段代码,主要观察三个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JHSDB_TestCase {
static class Test {
static ObjectHolder staticObj = new ObjectHolder();
ObjectHolder instanceObj = new ObjectHolder();

void foo() {
ObjectHolder localObj = new ObjectHolder();
System.out.println("done");
}
}

private static class ObjectHolder {

}

public static void main(String[] args) {
Test test = new JHSDB_TestCase.Test();
test.foo();
}
}

instanceObj显然是放在了堆里面,而staticObj是一个静态变量,所以放在方法区,而localObj则是一个局部变量,显然放在栈帧的局部变量表里面。接下来通过JHSDB来验证这一猜测。记得给虚拟机加上这些配置-Xmx10m -XX:+UseSerialGC -XX:+UseCompressedOops

运行到在控制台打印的那句的时候打个断点,然后通过JPS来查看进程号,就可以愉快进行分析了。

我们一共创建了三个ObjectHolder实例,而实例则必然是存放在堆中的(注意和上面变量进行区分)。我们的目的是查找出引用这三个对象的指针(即变量)存放在哪里。也就是是在哪个区域中的变量,指向了我们在堆中的这三个对象。

不玩了不玩了,一玩就程序死了。反正看看书知道结果正确就行了。

JConsole

直接双击打开JConsole,然后就会让你选择对应的进程,然后就可以看到这个进程的总览了:

image-20200105172840986

内存监控

使用的代码入下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;
import java.util.List;

public class OOMObjectTest {
static class OOMObject {
public byte[] placeholder = new byte[64 * 1024];
}

public static void fillHead(int num) throws InterruptedException {
List<OOMObject> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Thread.sleep(50);
list.add(new OOMObject());
}
System.gc();
}

public static void main(String[] args) {
try {
fillHead(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

可以看到一个placeholder是64K,然后编译并且执行

1
2
javac OOMObjectTest.java
java -Xms100m -Xmx100m -XX:+UseSerialGC OOMObjectTest

最终结果如图所示:

image-20200105183523146

因为我截图慢了,所以程序已经执行完了,所以会提示“连接失败”。

但是可以看出来Eden这个空间是折线形状的,且在最后的时候会发现Eden区域变成了0。

线程等待

线程等待用的代码是这样的,一个函数是模拟线程忙碌状态,弄个死循环就行了;另外一个函数锁定的状态,最后在main函数里面用用户的输入来确保能够检测到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ThreadWaitTest {
public static void createBusyThread() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//do nothing
}
}
}, "testBusyThread");
thread.start();
}

public static void createLockThread(final Object lock) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "testLockThread");
thread.start();
}

public static void main(String[] args) throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
bufferedReader.readLine();
createBusyThread();
bufferedReader.readLine();
Object object = new Object();
createLockThread(object);
}
}

然后编译并且执行这段代码:

1
2
javac ThreadWaitTest.java
java ThreadWaitTest

程序会等待用户的输入,此时去打开JConsole,然后找到这个进程连接上去,找到线程,再找到main,如图所示:

image-20200105190251045

可以看到main目前是在readByte这里,也就是在等待用户的输入。而且此时的状态是RUNNABLE,也就是线程是会被分配到CPU时间的。接下来随便输入点什么,这个时候testBusyThread就起来了,但是因为一直在循环里,所以是这样子的:

image-20200105190643687

而且这第九行正好就是while(ture)那行。然后继续随便输入点东西,这个时候就会如图所示:

image-20200105190856116

这个线程一直处于waiting状态,因为它需要等待lock的notify方法。这里并没有发生死锁,只是没有编写相关代码而已。具体的死锁这里略去。

VisualVM

大力发展的一个JVM分析软件。其在JDK下自带,名字叫jvisualvm.exe,macOS中我是自己下载的,使用体感一般,偶尔会卡死。