Java 虚拟机分析工具用 JDK 自带的 jconsole
, jvisualvm
, 和 jmc
(Java Mission Control) 就已经非常好了,还真极少情况下(甚至没有)非得用商业的 Profiler 工具如 YourKit Java Profiler 或 JProfiler 的情况。用于实时观察 JVM 的内存, CPU, 线程等运行状况,对比 Heap 快照,发现线程死锁的应用情景,我比较喜欢用 jvisualvm
(VisualVM)。
有很长一段时间,因为在家办公司,只要连接到公司的 VPN 后再执行 jvisualvm
来打开 VisualVM 时,会有很长的时间(可能长达 10 几分钟)卡在窗口右下角状态栏的 Computing description...
,要等到它消失后才能开始连接 JVM,这时候我的 Java 应用可能早就退出了。要是本地不连 VPN 的话就正常,启动 VisualVM 是正常的,但调试有些工作项目又必须连接公司的 VPN。
这种使用 VisualVM 的体验有如恶梦一般,还是有经常要用到 VisualVM 的需求,所以再也不能忍受这种无谓的等待。依然是 Google + StackOverflow 的模式,找到原来罪魁祸首是 /etc/hosts
中的 127.0.0.1
这个条目。
首先用 hostname
命令找到机器名称,比如是 yanbin-mac
, 然后在 /etc/hosts
中要有下面的第三行
1 2 3 |
127.0.0.1 localhost 255.255.255.255 broadcasthost 127.0.0.1 yanbin-mac |
而我的机器名还常常变化,所以时不时的要瞧瞧 /etc/hosts
中的内容。
这时候运气好的话,再次运行 jvisualvm
打开 VisualVM 后应该能立即可用。
给 VisualVM 加上调试日志输出
想要知道 jvisualvm 启动时后台在做些什么,可以加一个日志配置文件,然后启动时指定该配置文件,它是一个 JUL 的日志配置。比如我们在当前目录下创建文件 logging.properties
, 内容如下:
1 2 3 4 |
handlers= java.util.logging.ConsoleHandler .level= INFO java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter |
这时我们启动 jvisualvm
的命令就是
$ jvisualvm -J-Djava.util.logging.config.file=logging.properties
这启该该 jvisualvm 的终端就能看到 VisualVM
的运行时日志输出,比如不正常时看到类似这样的信息
java.rmi.ConnectException: Connection refused to host: xxx.xx.xx.xx; nested exception is:
java.net.ConnectException: Operation timed out (Connection timed out)
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:619)
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216)
at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:129)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:227)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:179)
at com.sun.proxy.$Proxy10.newClient(Unknown Source)
信息中的 xxx.xx.xx.xx
就是连接 VPN 后的网段,VPN 上有防火墙设置,Computing descrition...
估计就发生在此处的 Operation timed out
上。
JConsole 可以使用同样的日志配置
如果是启动 JConsole, 可使用相同的日志配置文件,但需要加上额外的 -debug
命令,完速启动命令如下:
$ jconsole -J-Djava.util.logging.config.file=logging.properties -debug
与 VisualVM 稍有不同的是,JConsole 不在启动它的终端显示调试日志,而是打开自己的一个 JConsole: Output
窗口来显示日志。
下面另一个 logging.properties
的文件内容参考
1 2 3 4 5 6 7 |
handlers=java.util.logging.ConsoleHandler .level=INFO java.util.logging.ConsoleHandler.level=FINEST java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter javax.management.level=FINEST javax.management.remote.level=FINEST |
其他
单独安装的 VisualVM 的配置文件在 /Applications/VisualVM.app/Contents/Resources/visualvm/etc/visualvm.conf. 可尝试用以下两个命令查看是否是有用的信息
$ lsof -p $(ps auxww | awk '$0 !~ /awk/ && $0 ~ /java.*visualvm/ {print $2}') | awk -F "/" '/hsperf/ {print $NF}'
$ jstack -l $(ps auxww | awk '$0 !~ /awk/ && $0 ~ /java.*visualvm/ {print $2}')
如果是单独安装的 VisualVM 把前两命令的 visualvm
改为 VisualVM.app
另外,借机记录一个 Mac OS X 平台下 JMC 在 JDK 8u162 开始的一个 bug, 就是运行 jmc
命令后容器一直龟缩在 dock 上显示不出来,用 JDK 8u152 没问题,8u211 问题依然存在,JDK 9 已解决。因为 JMC 的界面是用的 SWT,它与 JDK 版本存在兼容性问题。
链接: