编写java我们一般只会用到javac和java这两个命令,最多还会使用javadoc这个,但是实际上JDK里面包含了非常多有用的工具,这里打算介绍几种。
命令行工具
这些工具由于是命令行中执行的,所以各个系统上都可以执行。
jps
JPS,即Java process status tool,能够显示系统内所有的虚拟机进程,直接输入就可以看到结果。
注意由于它本身自己也是用java实现的,所以会显示出它自己(如果整个系统只有它一个java虚拟机在执行的话)

我的机器上执行了一个jar包,这个jar包的作用是生成一个随机数,然后对它进行因式分解,这里可以看到它的LVMID号是9547,而jps自身也有一个LVMID号是9636。而且由于是在同一台主机上,所以这个LVMID号和进程号其实是一样的。
最常用的就是jps -v,可以查看虚拟机启动的时候所带的参数了。
jstat
jstat,即JVM Statistic Monitoring Tool,用它可以显示出类的装载信息,垃圾收集信息等,在运行java程序的时候可以定位虚拟机性能问题。
接下来看看垃圾回收的性能查看:jstat -gc 9547 1000 3 这个的意思是查看垃圾收集情况,每1000毫秒一次,一共3次。

可以清楚看到各个区域所使用的内存量。
jinfo
jinfo(Configuration Info for java),这个能够实时调整虚拟机的各项参数,但是仅仅限于一部分在运行期可写的数据。
jmap
生成堆转储快照(dump文件)也可以用来查看共享库等信息。

PS:这是我第二次执行所以说文件已经存在了。
jhat
用来分析上面jmap生成的dump文件,但是由于这个分析过程一般很耗时,所以比较推荐在别的机器上执行。而且这个工具也比较简陋。
jstack
jstack(Stack Trace for java),可以生成所有线程的快照,这个快照一般称之为javacore文件,然后就可以进行分析了。
HSDIS
在介绍这东西前,首先先编译并且执行一段代码:
1 | public class Bar { |
编译之后用下面的虚拟机参数去执行:java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,Bar.sum -XX:CompileCommand=compileonly,Bar.sum Bar,报错,这是因为我没有安装这个插件,然后我去网上找这个插件的官网,结果….

可视化工具
上面介绍的这些工具都是在命令行中执行的,并不是非常直观,JDK还有两个很好用的可视化工具,分别是JHSDB、JConsole和VisualVM。
JHSDB
在这里开始前,需要先声明一下:变量,指针,对象(实例)这三者的关系。
Person p; 其中的p,称为一个变量。而这句话,就只是在内存中开辟了一小块空间
new Person();这句话会在堆里面创建一个实例,也被称作对象。
当上面的两句话合二为一,即Person p = new Person();,除了完成上面的两个,还同时让变量p的值,等于了对象在内存中的地址。
上来先有一段代码,主要观察三个变量:
1 | public class JHSDB_TestCase { |
instanceObj显然是放在了堆里面,而staticObj是一个静态变量,所以放在方法区,而localObj则是一个局部变量,显然放在栈帧的局部变量表里面。接下来通过JHSDB来验证这一猜测。记得给虚拟机加上这些配置-Xmx10m -XX:+UseSerialGC -XX:+UseCompressedOops
运行到在控制台打印的那句的时候打个断点,然后通过JPS来查看进程号,就可以愉快进行分析了。
我们一共创建了三个ObjectHolder实例,而实例则必然是存放在堆中的(注意和上面变量进行区分)。我们的目的是查找出引用这三个对象的指针(即变量)存放在哪里。也就是是在哪个区域中的变量,指向了我们在堆中的这三个对象。
不玩了不玩了,一玩就程序死了。反正看看书知道结果正确就行了。
JConsole
直接双击打开JConsole,然后就会让你选择对应的进程,然后就可以看到这个进程的总览了:

内存监控
使用的代码入下所示:
1 | import java.util.ArrayList; |
可以看到一个placeholder是64K,然后编译并且执行
1 | javac OOMObjectTest.java |
最终结果如图所示:

因为我截图慢了,所以程序已经执行完了,所以会提示“连接失败”。
但是可以看出来Eden这个空间是折线形状的,且在最后的时候会发现Eden区域变成了0。
线程等待
线程等待用的代码是这样的,一个函数是模拟线程忙碌状态,弄个死循环就行了;另外一个函数锁定的状态,最后在main函数里面用用户的输入来确保能够检测到。
1 | import java.io.BufferedReader; |
然后编译并且执行这段代码:
1 | javac ThreadWaitTest.java |
程序会等待用户的输入,此时去打开JConsole,然后找到这个进程连接上去,找到线程,再找到main,如图所示:

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

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

这个线程一直处于waiting状态,因为它需要等待lock的notify方法。这里并没有发生死锁,只是没有编写相关代码而已。具体的死锁这里略去。
VisualVM
大力发展的一个JVM分析软件。其在JDK下自带,名字叫jvisualvm.exe,macOS中我是自己下载的,使用体感一般,偶尔会卡死。