遇到这样一个问题: Java程序中有很多个HashSet. 想找到其元素数目不正常的HashSet的实例.
当然这个可以用各种工具来做(比如JProfiler等等).
这里用的是JDK自带的ServiceAbility Agent.
具体介绍可以参见
http://rednaxelafx.iteye.com/blog/1847971
首先写个简单的测试程序:
HashSet s = new HashSet<>();
for (int i=0; i<100; i++) {
s.add(i);
}
Thread.sleep(100000000);
运行, 使用JPS找到java进程的pid, 然后进入SA的GUI界面:
java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
然后,进入Tools - Find Object by Query, 输入查询语句
select a from java.util.HashSet a
和SQL很像吧.
这时列出了JDK中当前的所有HashSet的实例的地址. 随便点一个, 查看其内容:
正好就是size = 100的. 然后, 点开其中的tables, 就可以看到key是什么了.
至于为什么属性的顺序是map -> table, 原因就是HashSet其实是个HashMap来实现的.
OK, 那么问题来了: 在真正的产品中, 不可能像这个小测试程序一样, 只有这么几个HashSet. 那么, 如何找到目标HashSet呢?
有两种方法:
1. 上面的Find Object by Query, 是可以些where条件的, 但是我没有试验出来
2. 使用下面介绍的方法,就是使用Serviceability Agent的JS命令行工具.
在SA的JS命令行工具中, 提供了各种预定义的JS封装的工具类和工具函数, 可以查询JDK内部的各种信息. 感兴趣的可以查看sa-jdi.jar中的sun\jvm\hotspot\utilities\soql\sa.js
首先运行JS工具,连接到Java进程.这里同样用到了pid
C:\jdk8>java -cp .;./lib/sa-jdi.jar sun.jvm.hotspot.tools.soql.JSDB 7540
Attaching to process ID 7540, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
jsdb>
然后编辑如下的.js脚本:
print("loaded");
var oh = sa.vm.getObjectHeap();
var kls = findInstanceKlass("java.util.HashSet");
print("kls = " + kls);
forEachOopOfKlass(function(a){
// print(a.getHandle());
var o = object(addr2str(a.handle));
print("Size = " + o.map.size);
if (o.map.size >= 100) {
print("A set with at least 100 objs", a.handle);
}
}, kls, false)
为了方便, 可以在编辑器中编辑, 然后在jsdb中load这个脚本,比如
jsdb> load("c:\\dev\\a.js");
对于每个类的实例, 回调处理函数function(a) . 在这个回调函数中, 参数a就是每个实例的表示对象.
a.handle, 就是JDK内部a的地址 (和上面截图中SOQL查询出来的地址一致).
关键在于
var o = object(addr2str(a.handle));
这一行.
这一行根据sa.js内置的函数,将地址转换成字符串, 最后通过函数object, 得到具体的类的实例.
最后根据o.map.size >= 100, 就可以打印出来要查找的地址了.
最后, 得到查找的地址之后,可以在SA GUI工具的Tools - Inspect 中直接观察这个地址, 和之前在GUI工具中几个地址一个一个试验, 结果是一致的.