普通Java程式設計師學習使用的6個JDK內建工具

ImportNew發表於2015-12-29

與你的問題不同,我認為軟體工程主要是用來解決問題的。有些部落格認為“每個小孩都應該學習程式設計”,“你認為學數學只是玩玩而已?如果你有看過我的HTML5偵錯程式的話,你會發現我是一個程式設計師,但我做的工作遠不止數學這些”。 上面兩者都同意一個觀點,軟體工程不只是用計算機語言寫的一些隻言片語。軟體解決的問題詮釋了程式設計師的價值。

解決問題的最終進展來自科學、強化清晰的頭腦和我們一路以來使用的工具。

推薦普通開發者學習使用的6個JDK內建工具

你有沒有留意過那些 JDK 安裝附帶的工具?既然那些大牛同意把那些工具加到 JDK 裡,應該是有用的。

因此,在這篇文章裡,我挑了幾個 Hotspot 標準安裝後可用的小工具來介紹。我們決定忽略那些安全相關的和各種遠端方法呼叫(RMI)、applets、web-start、web-services 工具。讓我們把焦點放在那些普通開發者開發一般應用過程中可能有用的工具。注意,如果你只是對命令列工具感興趣,而不僅僅是Java相關的工具,這裡介紹了 5 個非常有用的命令列工具。

再次重申,下面雖然不是 JDK 工具完整列表,但是我們想給你一個精華版本。下面是你用這些命令可以完成的真正有用的事情。

0、javap

你可以給 javap(Java Class檔案反編譯器)傳遞這些有用的引數:

  • -I – 列印行數和區域性變數
  • -p – 列印包括非public在內的所有類和成員資訊,
  • -c – 列印方法位元組碼

比如在著名的“你真的懂 Classloader 嗎?”演講裡,當出現 NoSuchMethodException 錯誤時,我們可以執行以下命令來調查這個類究竟有哪些成員方法和獲取這個類所有想找的資訊:

javap -l -c -p Util2

推薦普通開發者學習使用的6個JDK內建工具

當除錯類內部資訊或者研究隨機位元組碼順序時,javap 非常有用。

1、jjs

推薦普通開發者學習使用的6個JDK內建工具

jjs命令可以啟動一個 JavaScript 命令終端,你可以把它當做計算器或者用隨機的JS字串測試JS的古怪用法。不要讓另一個 JavaScript 謎題讓你措手不及!

哈,看到剛剛發生了什麼了麼?但是 JavaScript 是另一個話題,只需要知道即使沒有 node.js 或瀏覽器你也可以用jjs知道JS是怎麼工作的。

2、jhat

Java堆分析工具(jhat)正如它名字描述的那樣:分析dump堆資訊。在下面的小例子裡,我們構造了一個 OutOfMemoryError ,然後給這個 java 程式指定 -XX:+HeapDumpOnOutOfMemoryError ,這樣執行時就會產生一個 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);
   }
 }
}

產生一個 OutOfMemoryError 很簡單(大部分情況下我們無意為之),我們只要不斷地製造不讓垃圾回收器起作用就可以了。

執行這段程式碼會產生如下輸出:

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 伺服器供我們檢視結果。

$ 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 的資料。

推薦普通開發者學習使用的6個JDK內建工具

在那個頁面我們可以通過堆資訊的柱狀圖瞭解究竟是什麼耗盡了記憶體。

推薦普通開發者學習使用的6個JDK內建工具

現在我們可以清晰地看到擁有 393567 結點的 HashMap 就是導致程式崩潰的元凶。雖然有更多可以檢查記憶體分佈使用情況和堆分析的工具,但是jhat是內建的,是分析的一個好的開端。

3、jmap

jmap 是一個記憶體對映工具,它提供了另外一種不需要引發 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);
   }
 }
}

注意,現在我們不要消耗大量的記憶體,只是比較早結束並在程式關閉鉤子裡等待不讓 JVM 退出。這樣就允許我們用 jmap 連線這個程式獲取珍貴的記憶體 dump。

因此你可以用 jmap 的兩個功能來實現,獲取堆統計資訊和觸發一個堆 dump。因此,當執行:

jmap -heap 1354(這裡 1354 是上面程式執行的程式號),就可以獲取一個很好的記憶體使用統計資訊:

$ 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 還可以簡單地觸發當前堆 dump,之後可以隨意進行分析。你可以像下面例子中的那樣,傳一個 -dump 引數給 jmap。

現在有了 dump 得到的檔案 heap.bin,就可以用你喜歡的記憶體分析工具來分析。

4、jps

jps 是顯示 Java 程式系統程式(PID)最常用的工具。它與平臺無關,非常好用。想象一下我們啟動了上面的程式,然後想用 jmap 連線它。這個時候我們需要程式的 PID,jps 正好的派上用場。

$ 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 選項可以列印同步和鎖的資訊。

$ 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資深產品工程師)

還有其它的嗎?

今天我們介紹了 JDK 發行預裝的超棒工具。相信我,將來某天你肯定會用到它們中的一些。所以,如果你有時間,你可以翻一翻它們的官方文件。

試著在不同的場景使用並愛上它們。

如果你想學一些超棒的非 JDK 附帶的工具,可以看看 JRebel ,它可以讓你馬上看到程式碼的改動效果,還可以看到我們新的產品 XRebel ,它可以像X光眼鏡一樣掃描你的 web 應用。

如果你知道開發最佳實踐中至關重要的小工具,在本文末尾發表評論或者在 twitter上@shelajev 分享一下這個工具的細節。

Bonus Section: References

獎勵環節:參考

下面是一個更加完整的 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留下你寶貴的評論。如果你知道一些我沒有提及的重要工具,請讓我知道。

相關文章