普通Java程序員學(xué)習(xí)使用的6個(gè)JDK內(nèi)建工具
與你的問題不同,我認(rèn)為軟件工程主要是用來解決問題的。有些博客認(rèn)為“每個(gè)小孩都應(yīng)該學(xué)習(xí)編程”,“你認(rèn)為學(xué)數(shù)學(xué)只是玩玩而已?如果你有看過我的HTML5調(diào)試器的話,你會(huì)發(fā)現(xiàn)我是一個(gè)程序員,但我做的工作遠(yuǎn)不止數(shù)學(xué)這些”。 上面兩者都同意一個(gè)觀點(diǎn),軟件工程不只是用計(jì)算機(jī)語言寫的一些只言片語。軟件解決的問題詮釋了程序員的價(jià)值。
解決問題的最終進(jìn)展來自科學(xué)、強(qiáng)化清晰的頭腦和我們一路以來使用的工具。
你有沒有留意過那些 JDK 安裝附帶的工具?既然那些大牛同意把那些工具加到 JDK 里,應(yīng)該是有用的。
因此,在這篇文章里,我挑了幾個(gè) Hotspot 標(biāo)準(zhǔn)安裝后可用的小工具來介紹。我們決定忽略那些安全相關(guān)的和各種遠(yuǎn)程方法調(diào)用(RMI)、applets、web-start、web-services 工具。讓我們把焦點(diǎn)放在那些普通開發(fā)者開發(fā)一般應(yīng)用過程中可能有用的工具。注意,如果你只是對(duì)命令行工具感興趣,而不僅僅是Java相關(guān)的工具,這里介紹了 5 個(gè)非常有用的命令行工具。
再次重申,下面雖然不是 JDK 工具完整列表,但是我們想給你一個(gè)精華版本。下面是你用這些命令可以完成的真正有用的事情。
0、javap
你可以給 javap(Java Class文件反編譯器)傳遞這些有用的參數(shù):
-
-I – 打印行數(shù)和局部變量
-
-p – 打印包括非public在內(nèi)的所有類和成員信息,
-
-c – 打印方法字節(jié)碼
比如在著名的“你真的懂 Classloader 嗎?”演講里,當(dāng)出現(xiàn) NoSuchMethodException 錯(cuò)誤時(shí),我們可以執(zhí)行以下命令來調(diào)查這個(gè)類究竟有哪些成員方法和獲取這個(gè)類所有想找的信息:
javap -l -c -p Util2
當(dāng)調(diào)試類內(nèi)部信息或者研究隨機(jī)字節(jié)碼順序時(shí),javap 非常有用。
1、jjs
jjs命令可以啟動(dòng)一個(gè) JavaScript 命令終端,你可以把它當(dāng)做計(jì)算器或者用隨機(jī)的JS字符串測(cè)試JS的古怪用法。不要讓另一個(gè) JavaScript 謎題讓你措手不及!
哈,看到剛剛發(fā)生了什么了么?但是 JavaScript 是另一個(gè)話題,只需要知道即使沒有 node.js 或?yàn)g覽器你也可以用jjs知道JS是怎么工作的。
2、jhat
Java堆分析工具(jhat)正如它名字描述的那樣:分析dump堆信息。在下面的小例子里,我們構(gòu)造了一個(gè) OutOfMemoryError ,然后給這個(gè) java 進(jìn)程指定 -XX:+HeapDumpOnOutOfMemoryError ,這樣運(yùn)行時(shí)就會(huì)產(chǎn)生一個(gè) dump 文件供我們分析。
- public class OhMyMemory {
- private static Map 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);
- }
- }
- }
產(chǎn)生一個(gè) OutOfMemoryError 很簡(jiǎn)單(大部分情況下我們無意為之),我們只要不斷地制造不讓垃圾回收器起作用就可以了。
運(yùn)行這段代碼會(huì)產(chǎn)生如下輸出:
- 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
不錯(cuò),我們現(xiàn)在有一個(gè)可供分析的文件了。我們對(duì)這個(gè)文件執(zhí)行jhat開始進(jìn)行分析,jhat 會(huì)分析這個(gè)文件開啟一個(gè) http 服務(wù)器供我們查看結(jié)果。
- $ 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 的數(shù)據(jù)。
在那個(gè)頁面我們可以通過堆信息的柱狀圖了解究竟是什么耗盡了內(nèi)存。
現(xiàn)在我們可以清晰地看到擁有 393567 結(jié)點(diǎn)的 HashMap 就是導(dǎo)致程序崩潰的元兇。雖然有更多可以檢查內(nèi)存分布使用情況和堆分析的工具,但是jhat是內(nèi)置的,是分析的一個(gè)好的開端。
3、jmap
jmap 是一個(gè)內(nèi)存映射工具,它提供了另外一種不需要引發(fā) OutOfMemoryErrors 就可以獲取堆 dump 文件的方法。我們稍微修改一下上面的程序看一下效果。
- public class OhMyMemory {
- private static Map 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);
- }
- }
- }
注意,現(xiàn)在我們不要消耗大量的內(nèi)存,只是比較早結(jié)束并在進(jìn)程關(guān)閉鉤子里等待不讓 JVM 退出。這樣就允許我們用 jmap 連接這個(gè)進(jìn)程獲取珍貴的內(nèi)存 dump。
因此你可以用 jmap 的兩個(gè)功能來實(shí)現(xiàn),獲取堆統(tǒng)計(jì)信息和觸發(fā)一個(gè)堆 dump。因此,當(dāng)執(zhí)行:
jmap -heap 1354(這里 1354 是上面程序運(yùn)行的進(jìn)程號(hào)),就可以獲取一個(gè)很好的內(nèi)存使用統(tǒng)計(jì)信息:
- $ 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.
- $ jmap -dump:live,format=b,file=heap.bin 1354
- Dumping heap to /Users/shelajev/workspace_idea/throwaway/heap.bin ...
- Heap dump file created
jmap 還可以簡(jiǎn)單地觸發(fā)當(dāng)前堆 dump,之后可以隨意進(jìn)行分析。你可以像下面例子中的那樣,傳一個(gè) -dump 參數(shù)給 jmap。
現(xiàn)在有了 dump 得到的文件 heap.bin,就可以用你喜歡的內(nèi)存分析工具來分析。
4、jps
jps 是顯示 Java 程序系統(tǒng)進(jìn)程(PID)最常用的工具。它與平臺(tái)無關(guān),非常好用。想象一下我們啟動(dòng)了上面的程序,然后想用 jmap 連接它。這個(gè)時(shí)候我們需要程序的 PID,jps 正好的派上用場(chǎng)。
- $ 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
我們發(fā)現(xiàn)大多數(shù)情況下,“-mlv” 參數(shù)組合起來***用。它會(huì)打印main方法的參數(shù)、完整包名、JVM 相關(guān)參數(shù)。這樣你就可以在一大堆相似的進(jìn)程中找到你想要的那個(gè)。
現(xiàn)在有了 dump 得到的文件 heap.bin,就可以用你喜歡的內(nèi)存分析工具來分析。
5、jstack
jstack 是一個(gè)生成指定 JVM 進(jìn)程的線程堆棧工具。當(dāng)你程序一直在那里轉(zhuǎn)圈圈,而你想找到線程到底做了什么導(dǎo)致死鎖,那么 jstack 最適合。
jstack 只有幾個(gè)參數(shù)選項(xiàng),如果你拿不準(zhǔn),把它們都加上。如果后面發(fā)現(xiàn)有些信息對(duì)你意義不大時(shí)可以調(diào)整參數(shù)限制它的輸出。
-F 選項(xiàng)可以用來強(qiáng)制 dump,這在進(jìn)程掛起時(shí)非常有用,-I 選項(xiàng)可以打印同步和鎖的信息。
- $ 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.
- ….
上面的輸出雖然看起來簡(jiǎn)單,但是它包含了每個(gè)線程的狀態(tài)和它當(dāng)前的堆棧的信息。
jstack 非常有用,我們?cè)谌粘9ぷ髦惺褂梅浅nl繁,特別是我們負(fù)責(zé)啟動(dòng)停止應(yīng)用服務(wù)器的測(cè)試引擎。測(cè)試工作往往不順利,jstack 可以讓我們知道 JVM 內(nèi)部的運(yùn)行狀態(tài)且沒有什么負(fù)面的影響。
— Neeme Praks(ZeroTurnaround資深產(chǎn)品工程師)
還有其它的嗎?
今天我們介紹了 JDK 發(fā)行預(yù)裝的超棒工具。相信我,將來某天你肯定會(huì)用到它們中的一些。所以,如果你有時(shí)間,你可以翻一翻它們的官方文檔。
試著在不同的場(chǎng)景使用并愛上它們。
如果你想學(xué)一些超棒的非 JDK 附帶的工具,可以看看 JRebel ,它可以讓你馬上看到代碼的改動(dòng)效果,還可以看到我們新的產(chǎn)品 XRebel ,它可以像X光眼鏡一樣掃描你的 web 應(yīng)用。
如果你知道開發(fā)***實(shí)踐中至關(guān)重要的小工具,在本文末尾發(fā)表評(píng)論或者在 twitter上@shelajev 分享一下這個(gè)工具的細(xì)節(jié)。
Bonus Section: References
獎(jiǎng)勵(lì)環(huán)節(jié):參考
下面是一個(gè)更加完整的 JDK 工具可用列表。雖然這不是一個(gè)完整的列表,為了節(jié)省篇幅,我們省掉了加密、web-services 相關(guān)的工具等。謝謝 manpagez.com 提供的資源。
-
jar — 一個(gè)創(chuàng)建和管理 jar 文件的工具。
-
java — Java 應(yīng)用啟動(dòng)器。在這篇文章里,開發(fā)和部署都是用的這個(gè)啟動(dòng)器。
-
javac — Java 編譯器。
-
javadoc — API 文檔生成器。
-
javah — native 本地方法中用于生成 C 語言頭文件和源文件。
-
javap — class 文件反編譯器。
-
jcmd — JVM 命令行診斷工具,可發(fā)送診斷命令請(qǐng)求到 JVM 中。
-
jconsole — 一個(gè)兼容 JMX 的監(jiān)控 JVM 的圖形化工具。可以監(jiān)控本地和遠(yuǎn)程 JVM,也可以監(jiān)控和管理單獨(dú)的一個(gè)應(yīng)用。
-
jdb — Java 調(diào)試器。
-
jps — JVM 進(jìn)程查看工具,列出了系統(tǒng)運(yùn)行的所有 hotspot JVM 進(jìn)程。
-
jstat — JVM 狀態(tài)監(jiān)控工具。它可以收集和打印指定的 JVM 進(jìn)程性能狀態(tài)。
-
jhat — 堆 dump 信息的瀏覽器,啟動(dòng)一個(gè) web 服務(wù)器來顯示你用諸如 jmap -dump 得到的堆 dump 信息。
-
jmap — Java 內(nèi)存映射工具,打印指定進(jìn)程、核心文件、遠(yuǎn)程調(diào)試服務(wù)器共享內(nèi)存映射或者堆內(nèi)存詳細(xì)信息。
-
jsadebugd — Java 服務(wù)調(diào)試守護(hù)進(jìn)程—依附到一個(gè) Java 進(jìn)程或核心文件并且擔(dān)當(dāng)一個(gè)調(diào)試服務(wù)器的作用。
-
jstack —Java 堆棧信息工具——打印指定進(jìn)程或核心文件或者遠(yuǎn)程調(diào)試服務(wù)器的線程堆棧。
-
jjs — 運(yùn)行 Nashorn 命令行腳本 shell。
-
jrunscript — Java 腳本運(yùn)行工具。不過你要心里有數(shù),這實(shí)際上是一個(gè)還沒支持的測(cè)試功能。未來的 JDK 版本里面可能會(huì)移除它。
希望上面的內(nèi)容對(duì)你們有幫助,你可以在 twitter 上 @ shelajev留下你寶貴的評(píng)論。如果你知道一些我沒有提及的重要工具,請(qǐng)讓我知道。