javap
你可以给 javap(Java class文件反编译器)传递这些有用的参数:
-
-I – 打印行数和局部变量
-
-p – 打印包括非public在内的所有类和成员信息,
-
-c – 打印方法字节码
比如在著名的“你真的懂 Classloader 吗?”演讲里,当出现 NoSuchMethodException 错误时,我们可以执行以下命令来调查这个类究竟有哪些成员方法和获取这个类所有想找的信息:
1
|
javap -l -c -p Util2
|
当调试类内部信息或者研究随机字节码顺序时,javap 非常有用。
1、jjs
jjs命令可以启动一个 JavaScript 命令终端,你可以把它当做计算器或者用随机的JS字符串测试JS的古怪用法。不要让另一个 JavaScript 谜题让你措手不及!
哈,看到刚刚发生了什么了么?但是 JavaScript 是另一个话题,只需要知道即使没有 node.js 或浏览器你也可以用jjs知道JS是怎么工作的。
2、jhat
Java堆分析工具(jhat)正如它名字描述的那样:分析dump堆信息。在下面的小例子里,我们构造了一个 OutOfMemoryError ,然后给这个 java 进程指定 -XX:+HeapDumpOnOutOfMemoryError ,这样运行时就会产生一个 dump 文件供我们分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
class
OhMyMemory {
private
static
Map<String, Integer> map =
new
HashMap<>();
public
static
void
main(String[] args) {
Runtime.getRuntime().addShutdownHook(
new
Thread() {
@Override
public
void
run() {
System.out.println(
"We have accumulated "
+ map.size() +
" entries"
);
}
}
);
for
(
int
i =
0
; ;i++) {
map.put(Integer.toBinaryString(i), i);
}
}
}
|
产生一个 OutOfMemoryError 很简单(大部分情况下我们无意为之),我们只要不断地制造不让垃圾回收器起作用就可以了。
运行这段代码会产生如下输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
org.shelajev.throwaway.jdktools.OhMyMemory
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid5644.hprof ...
Heap dump file created [
73169721
bytes in
0.645
secs]
Exception in thread
"main"
java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:
703
)
at java.util.HashMap.putVal(HashMap.java:
662
)
at java.util.HashMap.put(HashMap.java:
611
)
at org.shelajev.throwaway.jdktools.OhMyMemory.main(OhMyMemory.java:
24
)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
62
)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
43
)
at java.lang.reflect.Method.invoke(Method.java:
483
)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:
134
)
We have accumulated
393217
entries
|
不错,我们现在有一个可供分析的文件了。我们对这个文件执行jhat开始进行分析,jhat 会分析这个文件开启一个 http 服务器供我们查看结果。
1
2
3
4
5
6
7
8
9
10
11
|
shelajev
@shrimp
~/workspace_idea/throwaway
$ jhat java_pid5644.hprof
Reading from java_pid5644.hprof...
Dump file created Thu Aug
14
14
:
48
:
19
EEST
2014
Snapshot read, resolving...
Resolving
1581103
objects...
Chasing references, expect
316
dots...
Eliminating duplicate references........
Snapshot resolved.
Started HTTP server on port
7000
Server is ready.
|
可以通过访问 http://localhost:7000 来查看 dump 的数据。
在那个页面我们可以通过堆信息的柱状图了解究竟是什么耗尽了内存。
现在我们可以清晰地看到拥有 393567 结点的 HashMap 就是导致程序崩溃的元凶。虽然有更多可以检查内存分布使用情况和堆分析的工具,但是jhat是内置的,是分析的一个好的开端。
3、jmap
jmap 是一个内存映射工具,它提供了另外一种不需要引发 OutOfMemoryErrors 就可以获取堆 dump 文件的方法。我们稍微修改一下上面的程序看一下效果。
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
|
public
class
OhMyMemory {
private
static
Map<String, Integer> map =
new
HashMap<>();
public
static
void
main(String[] args) {
Runtime.getRuntime().addShutdownHook(
new
Thread() {
@Override
public
void
run() {
try
{
System.out.println(
"Enter something, so I'll release the process"
);
System.in.read();
System.out.println(
"We have accumulated "
+ map.size() +
" entries"
);
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
);
for
(
int
i =
0
; i <
10000
;i++) {
map.put(Integer.toBinaryString(i), i);
}
}
}
|
注意,现在我们不要消耗大量的内存,只是比较早结束并在进程关闭钩子里等待不让 JVM 退出。这样就允许我们用 jmap 连接这个进程获取珍贵的内存 dump。
因此你可以用 jmap 的两个功能来实现,获取堆统计信息和触发一个堆 dump。因此,当执行:
jmap -heap 1354(这里 1354 是上面程序运行的进程号),就可以获取一个很好的内存使用统计信息:
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
42
43
44
45
46
47
48
49
50
51
52
53
54
|
shelajev
@shrimp
~/workspace_idea/throwaway
$ jmap -heap
1354
Attaching to process ID
1354
, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is
25.0
-b70
using thread-local object allocation.
Parallel GC with
4
thread(s)
Heap Configuration:
MinHeapFreeRatio =
40
MaxHeapFreeRatio =
70
MaxHeapSize =
67108864
(
64
.0MB)
NewSize =
1572864
(
1
.5MB)
MaxNewSize =
22020096
(
21
.0MB)
OldSize =
45088768
(
43
.0MB)
NewRatio =
2
SurvivorRatio =
8
MetaspaceSize =
21807104
(
20
.796875MB)
CompressedClassSpaceSize =
1073741824
(
1024
.0MB)
MaxMetaspaceSize =
17592186044415
MB
G1HeapRegionSize =
0
(
0
.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity =
1048576
(
1
.0MB)
used =
628184
(
0
.5990829467773438MB)
free =
420392
(
0
.40091705322265625MB)
59.908294677734375
% used
From Space:
capacity =
524288
(
0
.5MB)
used =
491568
(
0
.4687957763671875MB)
free =
32720
(
0
.0312042236328125MB)
93.7591552734375
% used
To Space:
capacity =
524288
(
0
.5MB)
used =
0
(
0
.0MB)
free =
524288
(
0
.5MB)
0.0
% used
PS Old Generation
capacity =
45088768
(
43
.0MB)
used =
884736
(
0
.84375MB)
free =
44204032
(
42
.15625MB)
1.9622093023255813
% used
981
interned Strings occupying
64824
bytes.
shelajev
@shrimp
~/workspace_idea/throwaway
$ jmap -dump:live,format=b,file=heap.bin
1354
Dumping heap to /Users/shelajev/workspace_idea/throwaway/heap.bin ...
Heap dump file created
|
jmap 还可以简单地触发当前堆 dump,之后可以随意进行分析。你可以像下面例子中的那样,传一个 -dump 参数给 jmap。
现在有了 dump 得到的文件 heap.bin,就可以用你喜欢的内存分析工具来分析。
4、jps
jps 是显示 Java 程序系统进程(PID)最常用的工具。它与平台无关,非常好用。想象一下我们启动了上面的程序,然后想用 jmap 连接它。这个时候我们需要程序的 PID,jps 正好的派上用场。
1
2
3
4
5
|
shelajev
@shrimp
~/workspace_idea/throwaway
$ jps -mlv
5911
com.intellij.rt.execution.application.AppMain org.shelajev.throwaway.jdktools.OhMyMemory -Xmx64m -Didea.launcher.port=
7535
-Didea.launcher.bin.path=/Applications/IntelliJ IDEA
14
EAP.app/Contents/bin -Dfile.encoding=UTF-
8
5544
-Dfile.encoding=UTF-
8
-ea -Dsun.io.useCanonCaches=
false
-Djava.net.preferIPv4Stack=
true
-Djsse.enableSNIExtension=
false
-XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=
50
-XX:+HeapDumpOnOutOfMemoryError -Xverify:none -Xbootclasspath/a:../lib/boot.jar -Xms128m -Xmx750m -XX:MaxPermSize=350m -XX:ReservedCodeCacheSize=225m -XX:+UseCompressedOops -agentlib:yjpagent=probe_disable=*,disablealloc,disabletracing,onlylocal,disableexceptiontelemetry,delay=
10000
,sessionname=IntelliJIdea14 -Didea.java.redist=NoJavaDistribution -Didea.home.path=/Applications/IntelliJ IDEA
14
EAP.app/Contents -Didea.paths.selector=IntelliJIdea14
5930
sun.tools.jps.Jps -mlvV -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.
8.0
.jdk/Contents/Home -Xms8m
|
我们发现大多数情况下,“-mlv” 参数组合起来最好用。它会打印main方法的参数、完整包名、JVM 相关参数。这样你就可以在一大堆相似的进程中找到你想要的那个。
现在有了 dump 得到的文件 heap.bin,就可以用你喜欢的内存分析工具来分析。
5、jstack
jstack 是一个生成指定 JVM 进程的线程堆栈工具。当你程序一直在那里转圈圈,而你想找到线程到底做了什么导致死锁,那么 jstack 最适合。
jstack 只有几个参数选项,如果你拿不准,把它们都加上。如果后面发现有些信息对你意义不大时可以调整参数限制它的输出。
-F 选项可以用来强制 dump,这在进程挂起时非常有用,-I 选项可以打印同步和锁的信息。
1
2
3
4
5
6
7
8
9
10
|
shelajev
@shrimp
~/workspace_idea/throwaway
$ jstack -F -l
9153
Attaching to process ID
9153
, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is
25.0
-b70
Deadlock Detection:
No deadlocks found.
….
|
上面的输出虽然看起来简单,但是它包含了每个线程的状态和它当前的堆栈的信息。
jstack 非常有用,我们在日常工作中使用非常频繁,特别是我们负责启动停止应用服务器的测试引擎。测试工作往往不顺利,jstack 可以让我们知道 JVM 内部的运行状态且没有什么负面的影响。
— Neeme Praks(ZeroTurnaround资深产品工程师)
6、还有其它的吗?
今天我们介绍了 JDK 发行预装的超棒工具。相信我,将来某天你肯定会用到它们中的一些。所以,如果你有时间,你可以翻一翻它们的官方文档。
试着在不同的场景使用并爱上它们。
如果你想学一些超棒的非 JDK 附带的工具,可以看看 JRebel ,它可以让你马上看到代码的改动效果,还可以看到我们新的产品 XRebel ,它可以像X光眼镜一样扫描你的 web 应用。
如果你知道开发最佳实践中至关重要的小工具,在本文末尾发表评论或者在 twitter上@shelajev 分享一下这个工具的细节。
7、参考与引用
下面是一个更加完整的 JDK 工具可用列表。虽然这不是一个完整的列表,为了节省篇幅,我们省掉了加密、web-services 相关的工具等。谢谢 manpagez.com 提供的资源。
-
jar — 一个创建和管理 jar 文件的工具。
-
java — Java 应用启动器。在这篇文章里,开发和部署都是用的这个启动器。
-
javac — Java 编译器。
-
javadoc — API 文档生成器。
-
javah — native 本地方法中用于生成 C 语言头文件和源文件。
-
javap — class 文件反编译器。
-
jcmd — JVM 命令行诊断工具,可发送诊断命令请求到 JVM 中。
-
jconsole — 一个兼容 JMX 的监控 JVM 的图形化工具。可以监控本地和远程 JVM,也可以监控和管理单独的一个应用。
-
jdb — Java 调试器。
-
jps — JVM 进程查看工具,列出了系统运行的所有 hotspot JVM 进程。
-
jstat — JVM 状态监控工具。它可以收集和打印指定的 JVM 进程性能状态。
-
jhat — 堆 dump 信息的浏览器,启动一个 web 服务器来显示你用诸如 jmap -dump 得到的堆 dump 信息。
-
jmap — Java 内存映射工具,打印指定进程、核心文件、远程调试服务器共享内存映射或者堆内存详细信息。
-
jsadebugd — Java 服务调试守护进程—依附到一个 Java 进程或核心文件并且担当一个调试服务器的作用。
-
jstack —Java 堆栈信息工具——打印指定进程或核心文件或者远程调试服务器的线程堆栈。
-
jjs — 运行 Nashorn 命令行脚本 shell。
-
jrunscript — Java 脚本运行工具。不过你要心里有数,这实际上是一个还没支持的测试功能。未来的 JDK 版本里面可能会移除它。
希望上面的内容对你们有帮助,你可以在 twitter 上 @ shelajev留下你宝贵的评论。如果你知道一些我没有提及的重要工具,请让我知道。