這篇文章優銳課主要和大家講講Java性能調優指南——有關提高Java代碼性能的各種技巧。
1. 介紹
在Java世界中,我們大多數人習慣於在Java應用程序開發的所有階段使用GUI工具:編寫代碼,對其進行調試和分析。我們通常更喜歡在開發環境中設置服務器環境,並嘗試使用熟悉的工具在本地重現問題。不幸的是,由於各種原因,通常不可能在本地重現一些問題。例如,你可能無權訪問服務器應用程序處理的真實客戶端數據。
在這種情況下,你需要在服務器盒上遠程對應用程序進行故障排除。你應該記住,你無法使用裸露的JRE來正確地對應用程序進行故障排除:它包含所有故障排除功能,但是實際上無法訪問它。結果,你需要在同一盒子上使用JDK或某些第三方工具。本文將介紹JDK工具,因爲與許多組織中需要安全審覈的任何第三方工具相比,你可能被允許在生產環境中使用它。
通常,僅需將JDK發行包解壓縮到你的包裝盒中就足夠了——你不需要出於故障排除的目的而正確安裝它(實際上,在很多情況下不希望正確安裝)。 對於基於JMX的功能,你實際上可以安裝任何Java 7/8 JDK,但是某些工具無法識別將來的JDK,因此我建議你安裝最新的Java 7/8 JDK或與服務器JRE完全匹配的內部版本-它允許 你會爲當前沒有訪問安全點的應用程序轉儲應用程序堆(某些處於空閒模式的應用程序是“無安全點”應用程序的簡單示例)。
2. 故障排除方案
2.1. 獲取正在運行的JVM的列表
爲了開始工作,你幾乎總是需要獲取正在運行的JVM,它們的進程ID和命令行參數的列表。 有時可能就足夠了:你可能會發現同一應用程序的第二個實例同時執行相同的工作(並損壞輸出文件/重新打開套接字/執行其他一些愚蠢的操作)。
只需運行jcmd而無需任何參數。 它將向你顯示正在運行的JVM的列表:
3824 org.jetbrains.idea.maven.server.RemoteMavenServer
2196
780 sun.tools.jcmd.JCmd
現在,你可以通過運行jcmd <PID> help命令來查看哪些診斷命令可用於給定的JVM。 這是VisualVM的示例輸出:
>jcmd 3036 help
3036:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help
鍵入jcmd <PID> <COMMAND_NAME>來運行診斷命令或得到一條錯誤消息,詢問命令參數:
>jcmd 3036 GC.heap_dump
3036:
java.lang.IllegalArgumentException: The argument 'filename' is mandatory.
你可以使用以下命令獲取有關診斷命令參數的更多信息:jcmd <PID>幫助<COMMANDNAME>。 例如,這是GC.heap_dump命令的輸出:
>jcmd 3036 help GC.heap_dump
3036:
GC.heap_dump
Generate a HPROF format dump of the Java heap.
Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified.
Permission: java.lang.management.ManagementPermission(monitor)
Syntax : GC.heap_dump [options] <filename>
Arguments:
filename : Name of the dump file (STRING, no default value)
Options: (options must be specified using the <key> or <key>=<value> syntax)
-all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)
2.2. 進行堆轉儲
jcmd
爲你提供了一個方便的界面,用於以
HPROF
格式進行堆轉儲。只需運行
jcmd <PID> GC.heap_dump <FILENAME>
。請注意,文件名是相對於正在運行的
JVM
當前目錄而不是當前目錄的,因此你可能需要指定完整路徑。最好使用
.hprof
擴展名作爲轉儲文件名。
線程轉儲完成後,你可以將文件複製到自己的盒子中,然後在
VisualVM
(它是
JDK
的一部分)中打開它,並使用其堆
walker
和查詢語言功能,或將其加載到
Java Mission Control
的
JOverflow
插件中並對其進行分析各種內存問題。
注意1:當然,還有許多其他工具可以處理hprof文件:NetBeans,Eclipse Memory Analyzer,YourKit等。將.hprof文件下載到框中後,請使用你喜歡的工具。
注意2:你也可以使用jmap工具進行堆轉儲:jmap -dump:live,file = <FILE_NAME> <PID>。問題在於它被正式證明爲不受支持。我們中的許多人都認爲JDK中不受支持的內容將永遠存在,但事實證明情況不再如此:JEP 240,JEP 241
2.3. 分析類直方圖
如果你正在尋找內存泄漏,通常只對堆中某些特定類型的活動對象感興趣。 例如,你可能知道一次只能擁有一個特定類型的對象(應用程序中的某種主要工作類)。 在舊的一代中可能還存在一個或多個相同類的實例,到目前爲止,這些實例尚未進行垃圾回收,但是不應從應用程序根目錄訪問它們。
要打印類直方圖,請運行以下兩個命令之一(兩個命令均打印活動對象的數量):
jcmd <PID> GC.class_histogram
jmap -histo:live <PID>
以下是示例輸出的前幾行:
num #instances #bytes class name
----------------------------------------------
1: 5923 5976952 [I
2: 50034 4127704 [C
3: 49465 1187160 java.lang.String
4: 188 1069496 [J
5: 3985 1067240 [Ljava.util.HashMap$Node;
6: 8756 982872 java.lang.Class
7: 2855 835792 [B
8: 23570 754240 java.util.HashMap$Node
9: 13964 671440 [Ljava.lang.Object;
10: 9642 308544 java.util.Hashtable$Entry
11: 4453 213744 java.util.HashMap
請注意,以字節爲單位的已佔用大小是一個較淺的大小–它不包含任何子對象。 很容易從char [](類名= [C]和String stats)中注意到這一事實–儘管實例數是相似的(儘管char []-s總是比String多,但是char []-的大小 s明顯更大,如果String的大小包含基礎char []的大小,則情況並非如此。
現在,你可以grep /搜索你感興趣的類名稱,並檢查活動實例的數量。 如果看到的實例超出預期,請進行堆轉儲並在任何堆遍歷器中對其進行分析(請參見上文)。
2.4. 進行線程轉儲
有時,你的應用程序可能會報告爲“not doing anything/got stuck”。有很多種“stuck”的情況——死鎖,高資源爭用或僅是O(N
10
)
算法來處理數百萬用戶的請求,在所有這些情況下,你應該知道你的應用程序線程正在執行什麼以及鎖將執行什麼操作他們持有。
有兩種類型的鎖:基於同步關鍵字和Object.wait / notifyAll方法的原始鎖,以及Java 5中引入的java.util.concurrent鎖。它們之間的主要區別是前者綁定到你輸入的堆棧框架上同步部分,並且在線程轉儲中始終可用。另一方面,後者(java.util.concurrent)不受堆棧框架限制——你可以使用一種方法輸入鎖,然後將其保留在另一種方法中。結果,一段時間以來,它們根本沒有在線程轉儲中打印,即使現在它們仍然是一個選項。但是,你需要在線程轉儲中同時使用兩種鎖,以正確調查線程問題。
有3種打印應用程序線程轉儲的方法。你可以在Linux上運行kill -3 <PID>。 或者,你可以在任何平臺上運行以下命令之一:
jstack <PID>
jcmd <PID> Thread.print
2.5. 運行Java Flight Recorder
到目前爲止,本文中提到的所有工具僅應用於快速調查。 爲了進行更深入的分析,我建議使用內置的Java Flight Recorder。
運行JFR是一個三步過程:
你需要創建一個包含所需設置的JFR模板文件。爲此,請運行jmc並轉到“窗口”->“飛行記錄模板管理器”菜單。配置文件準備就緒後,將其導出到文件中,然後將其發送到你正在使用的框中。
JFR需要JDK商業許可證。現在,你需要在所需的JVM上解鎖商業功能:
jcmd <PID> VM.unlock_commercial_features
之後,你可以啓動JFR。 這是命令行示例:
jcmd <PID> JFR.start name=test duration=60s settings=template.jfc filename=output.jfr
此命令立即運行JFR(未設置delay屬性),並使用template.jfc模板文件中的設置並將結果寫入output.jfr(在兩個文件中都使用絕對路徑),收集JVM信息60秒鐘。
錄製完成後,你可以將.jfr文件複製到筆記本電腦並在jmcGUI中對其進行分析。它包含幾乎所有你需要對JVM進行故障排除的信息,除了完整堆轉儲,你可以單獨創建並複製到你的機器中。
感謝閱讀!更深入探討歡迎留言或私信。
抽絲剝繭 細說架構那些事——【優銳課】