1、JDK命令行工具
1.1、jps命令
jps用於列出Java的進程,jps可以增加參數,-m用於輸出傳遞給Java進程的參數,-l用於輸出主函數的完整路徑,-v可以用於顯示傳遞給jvm的參數。
jps -l -m -v 31427 sun.tools.jps.Jps -l -m -v -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home -Xms8m
1.2、jstat命令
jstat是一個可以用於觀察Java應用程序運行時信息的工具,它的功能非常強大,可以通過它查看堆信息的詳細情況,它的基本使用方法為:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
選項option可以由以下值組成:
jstat -class pid:顯示加載class的數量,及所占空間等信息。 jstat -compiler pid:顯示VM實時編譯的數量等信息。 jstat -gc pid:可以顯示gc的信息,查看gc的次數,及時間。其中最后五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。 jstat -gccapacity:可以顯示,VM內存中三代(young,old,perm)對象的使用和占用大小,如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存占用量,PC是但前perm內存占用量。其他的可以根據這個類推, OC是old內純的占用量。 jstat -gcnew pid:new對象的信息。 jstat -gcnewcapacity pid:new對象的信息及其占用量。 jstat -gcold pid:old對象的信息。 jstat -gcoldcapacity pid:old對象的信息及其占用量。 jstat -gcpermcapacity pid: perm對象的信息及其占用量。 jstat -gcutil pid:統計gc信息統計。 jstat -printcompilation pid:當前VM執行的信息。 除了以上一個參數外,還可以同時加上 兩個數字,如:jstat -printcompilation 3024 250 6是每250毫秒打印一次,一共打印6次。
這些參數中最常用的參數是gcutil,下面是該參數的輸出介紹以及一個簡單例子:
S0 — Heap上的 Survivor space 0 區已使用空間的百分比 S1 — Heap上的 Survivor space 1 區已使用空間的百分比 E — Heap上的 Eden space 區已使用空間的百分比 O — Heap上的 Old space 區已使用空間的百分比 P — Perm space 區已使用空間的百分比 YGC — 從應用程序啟動到采樣時發生 Young GC 的次數 YGCT– 從應用程序啟動到采樣時 Young GC 所用的時間(單位秒) FGC — 從應用程序啟動到采樣時發生 Full GC 的次數 FGCT– 從應用程序啟動到采樣時 Full GC 所用的時間(單位秒) GCT — 從應用程序啟動到采樣時用於垃圾回收的總時間(單位秒) 實例使用1: [root@localhost bin]# jstat -gcutil 25444 S0 S1 E O P YGC YGCT FGC FGCT GCT 11.63 0.00 56.46 66.92 98.49 162 0.248 6 0.331 0.579
1.3、jinfo命令
jinfo可以用來查看正在運行的Java應用程序的擴展參數,甚至在運行時修改部分參數,它的基本語法為:
jinfo <option> <pid>
jinfo可以查看運行時參數:
jinfo -flag MaxTenuringThreshold 31518 -XX:MaxTenuringThreshold=15
jinfo還可以在運行時修改參數值:
> jinfo -flag PrintGCDetails 31518 -XX:-PrintGCDetails > jinfo -flag +PrintGCDetails 31518 > jinfo -flag PrintGCDetails 31518 -XX:+PrintGCDetails
1.4、jmap命令
jmap命令主要用於生成堆快照文件,它的使用方法如下:
> jmap -dump:format=b,file=heap.hprof 31531 Dumping heap to /Users/caojie/heap.hprof ... Heap dump file created
獲得堆快照文件之后,我們可以使用多種工具對文件進行分析,例如jhat,visual vm等。
1.5、jhat命令
使用jhat工具可以分析Java應用程序的堆快照文件,使用命令如下:
> jhat heap.hprof Reading from heap.hprof... Dump file created Tue Nov 11 06:02:05 CST 2014 Snapshot read, resolving... Resolving 8781 objects... Chasing references, expect 1 dots. Eliminating duplicate references. Snapshot resolved. Started HTTP server on port 7000 Server is ready.
jhat在分析完成之后,使用HTTP服務器展示其分析結果,在瀏覽器中訪問http://127.0.0.1:7000/即可得到分析結果。
1.6、jstack命令
jstack可用於導出Java應用程序的線程堆棧信息,語法為:
jstack -l <pid>
jstack可以檢測死鎖,下例通過一個簡單例子演示jstack檢測死鎖的功能。java代碼如下:
public class DeadLock extends Thread { protected Object myDirect; static ReentrantLock south = new ReentrantLock(); static ReentrantLock north = new ReentrantLock(); public DeadLock(Object obj) { this.myDirect = obj; if (myDirect == south) { this.setName("south"); } if (myDirect == north) { this.setName("north"); } } @Override public void run() { if (myDirect == south) { try { north.lockInterruptibly(); try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } south.lockInterruptibly(); System.out.println("car to south has passed"); } catch (InterruptedException e1) { System.out.println("car to south is killed"); } finally { if (north.isHeldByCurrentThread()) north.unlock(); if (south.isHeldByCurrentThread()) south.unlock(); } } if (myDirect == north) { try { south.lockInterruptibly(); try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } north.lockInterruptibly(); System.out.println("car to north has passed"); } catch (InterruptedException e1) { System.out.println("car to north is killed"); } finally { if (north.isHeldByCurrentThread()) north.unlock(); if (south.isHeldByCurrentThread()) south.unlock(); } } } public static void main(String[] args) throws InterruptedException { DeadLock car2south = new DeadLock(south); DeadLock car2north = new DeadLock(north); car2south.start(); car2north.start(); Thread.sleep(1000); } }
使用jps命令查看進程號為32627,然后使用jstack -l 32637 > a.txt命令把堆棧信息打印到文件中,該文件內容如下:
2014-11-11 21:33:12 Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.55-b03 mixed mode): "Attach Listener" daemon prio=5 tid=0x00007f8d0c803000 nid=0x3307 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "DestroyJavaVM" prio=5 tid=0x00007f8d0b80b000 nid=0x1903 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "north" prio=5 tid=0x00007f8d0c075000 nid=0x5103 waiting on condition [0x0000000115b06000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007d55ab600> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:894) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340) at DeadLock.run(DeadLock.java:48) Locked ownable synchronizers: - <0x00000007d55ab5d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) "south" prio=5 tid=0x00007f8d0c074800 nid=0x4f03 waiting on condition [0x0000000115a03000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007d55ab5d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:894) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340) at DeadLock.run(DeadLock.java:28) Locked ownable synchronizers: - <0x00000007d55ab600> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) "Service Thread" daemon prio=5 tid=0x00007f8d0c025800 nid=0x4b03 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "C2 CompilerThread1" daemon prio=5 tid=0x00007f8d0c025000 nid=0x4903 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "C2 CompilerThread0" daemon prio=5 tid=0x00007f8d0d01b000 nid=0x4703 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "Signal Dispatcher" daemon prio=5 tid=0x00007f8d0c022000 nid=0x4503 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "Finalizer" daemon prio=5 tid=0x00007f8d0d004000 nid=0x3103 in Object.wait() [0x000000011526a000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007d5505568> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) - locked <0x00000007d5505568> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:189) Locked ownable synchronizers: - None "Reference Handler" daemon prio=5 tid=0x00007f8d0d001800 nid=0x2f03 in Object.wait() [0x0000000115167000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007d55050f0> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x00000007d55050f0> (a java.lang.ref.Reference$Lock) Locked ownable synchronizers: - None "VM Thread" prio=5 tid=0x00007f8d0b83b000 nid=0x2d03 runnable "GC task thread#0 (ParallelGC)" prio=5 tid=0x00007f8d0b818000 nid=0x2503 runnable "GC task thread#1 (ParallelGC)" prio=5 tid=0x00007f8d0b819000 nid=0x2703 runnable "GC task thread#2 (ParallelGC)" prio=5 tid=0x00007f8d0d000000 nid=0x2903 runnable "GC task thread#3 (ParallelGC)" prio=5 tid=0x00007f8d0d001000 nid=0x2b03 runnable "VM Periodic Task Thread" prio=5 tid=0x00007f8d0c02e800 nid=0x4d03 waiting on condition JNI global references: 109 Found one Java-level deadlock: ============================= "north": waiting for ownable synchronizer 0x00000007d55ab600, (a java.util.concurrent.locks.ReentrantLock$NonfairSync), which is held by "south" "south": waiting for ownable synchronizer 0x00000007d55ab5d0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync), which is held by "north" Java stack information for the threads listed above: =================================================== "north": at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007d55ab600> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:894) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340) at DeadLock.run(DeadLock.java:48) "south": at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007d55ab5d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:894) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340) at DeadLock.run(DeadLock.java:28) Found 1 deadlock.
從這個輸出可以知道:
1、在輸出的最后一段,有明確的"Found one Java-level deadlock"輸出,所以通過jstack命令我們可以檢測死鎖;
2、輸出中包含了所有線程,除了我們的north,sorth線程外,還有"Attach Listener", "C2 CompilerThread0", "C2 CompilerThread1"等等;
3、每個線程下面都會輸出當前狀態,以及這個線程當前持有鎖以及等待鎖,當持有與等待造成循環等待時,將導致死鎖。
1.7、jstatd命令
jstatd命令是一個RMI服務器程序,它的作用相當於代理服務器,建立本地計算機與遠程監控工具的通信,jstatd服務器能夠將本機的Java應用程序信息傳遞到遠程計算機,由於需要多台計算機做演示,此處略。
1.8、hprof工具
hprof工具可以用於監控Java應用程序在運行時的CPU信息和堆信息,關於hprof的官方文檔如下:https://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html
2、Visual VM工具
Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具,它集成了多種性能統計工具的功能,使用Visual VM可以替代jstat、jmap、jhat、jstack等工具。在命令行輸入jvisualvm即可啟動visualvm。
打開Visual VM之后,左邊導航欄會顯示出當前機器所有Java進程:
點擊你想監控的程序即可對該程序進行監控,Visual VM的性能監控頁一共有以下幾個tab頁:
概述頁會顯示程序的基本使用情況,比如,進程ID,系統屬性,啟動參數等。
通過監視頁面,可以監視應用程序的CPU、堆、永久區、類加載器和線程數的整體情況,通過頁面上的Perform GC和Heap Dump按鈕還可以手動執行Full GC和生成堆快照。
線程頁面會提供詳細的線程信息,單擊Thread Dump按鈕可以導出當前所有線程的堆棧信息,如果Visual VM在當前線程中找到死鎖,則會以十分顯眼的方式在Threads頁面給予提示。
抽樣器可以對CPU和內存兩個性能進行抽樣,用於實時地監控程序。CPU采樣器可以將CPU占用時間定位到方法,內存采樣器可以查看當前程序的堆信息。下面是一個頻繁調用的Java程序,我們會對改程序進行采樣:
public class MethodTime { static java.util.Random r=new java.util.Random(); static Map<String,String> map=null; static{ map=new HashMap<String,String>(); map.put("1", "Java"); map.put("2", "C++"); map.put("3", "Delphi"); map.put("4", "C"); map.put("5", "Phython"); } public String getNameById(String id){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return map.get(id); } public List<String> getNamesByIds(String ids){ List<String> re=new ArrayList<String>(); String[] strs=ids.split(","); for(String id:strs){ re.add(getNameById(id)); } return re; } public List<String> getNamesByIdsBad(String ids){ List<String> re=new ArrayList<String>(); String[] strs=ids.split(","); for(String id:strs){ //A bad code getNameById(id); re.add(getNameById(id)); } return re; } public class NamesByIdsThread implements Runnable{ @Override public void run() { try{ while(true){ int c=r.nextInt(4); String ids=""; for(int i=0;i<c;i++) ids=Integer.toString((r.nextInt(4)+1))+","; getNamesByIds(ids); } }catch(Exception e){ } } } public class NamesByIdsBadThread implements Runnable{ @Override public void run() { try{ while(true){ int c=r.nextInt(4); String ids=""; for(int i=0;i<c;i++) ids=Integer.toString((r.nextInt(4)+1))+","; getNamesByIdsBad(ids); } }catch(Exception e){ } } } public static void main(String args[]){ MethodTime instance=new MethodTime(); new Thread(instance.new NamesByIdsThread()).start(); new Thread(instance.new NamesByIdsBadThread()).start(); } }
通過Visual VM的采樣功能,可以找到改程序中占用CPU時間最長的方法:
默認Visual VM不統計內置對象的函數調用,比如java.*包中的類,如果要統計這些內置對象,單機右上角的設置進行調配。Visual VM雖然可以統計方法的調用時間,但是無法給出方法調用堆棧,Jprofile不僅可以給出方法調用時間,還可以給出方法調用堆棧,較Visual VM更強大。
右擊左導航的應用程序,會出現以下菜單:
單機應用程序快照,可以分析當前應用程序的快照,單擊堆Dump能夠對當前的堆信息進行分析。Visual VM的更多使用方法,可以查看Oracle的官方文檔https://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/index.html
BTrace插件
BTrace是一款功能強大的性能檢測工具,它可以在不停機的情況下,通過字節碼注入,動態監控系統的運行情況,它可以跟蹤指定的方法調用、構造函數調用和系統內存等信息,本部分打算舉一個例子,講解一下BTrace的使用。要在Visual VM中使用Btrace,首先需要安裝Btrace插件,點擊工具->插件即可在線安裝,安裝后右鍵應用程序,就會出現如下選項:
點擊Trace application,即可進入BTrace插件界面。使用BTrace可以監控指定函數的耗時,以下腳本通過正則表達式,監控所有類的getNameById方法:
import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; @BTrace public class TracingScript { @TLS private static long startTime = 0; @OnMethod(clazz="/.+/", method="/getNameById/")//監控任意類的getNameById方法 public static void startMethod() { startTime=timeMillis(); } @OnMethod(clazz="/.+/", method="/getNameById/", location=@Location(Kind.RETURN))//方法返回時觸發 public static void endMethod() { print(strcat(strcat(name(probeClass()), "."), probeMethod())); print(" ["); print(strcat("Time taken : ", str(timeMillis() - startTime))); println("]"); } }
點擊運行,部分輸出如下:
MethodTime.getNameById [Time taken : 5] MethodTime.getNameById [Time taken : 4] MethodTime.getNameById [Time taken : 7] MethodTime.getNameById [Time taken : 7]
BTrace除了可以監控函數耗時外,還可以指定程序運行到某一行代碼觸發某一行為,定時觸發行為,監控函數參數等等。
3、MAT內存分析工具
MAT是一款功能強大的Java堆內存分析器,可以用於查找內存泄露以及查看內存消耗情況,MAT的官方文檔如下:http://help.eclipse.org/luna/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html。
在MAT中有淺堆和深堆的概念,淺堆是指一個對象結構所占用的內存大小,深堆是指一個對象被GC回收后可以真正釋放的內存大小。
通過MAT,可以列出所有垃圾回收的根對象,Java系統的根對象可能是以下類:系統類,線程,Java局部變量,本地棧等等。在MAT中還可以很清楚的看到根對象到當前對象的引用關系鏈。
MAT還可以自動檢測內存泄露,單擊菜單上的Leak Suspects命令,MAT會自動生成一份報告,這份報告羅列了系統內可能存在內存泄露的問題點。
在MAT中,還可以自動查找並顯示消耗內存最多的幾個對象,這些消耗大量內存的大對象往往是解決系統性能問題的關鍵所在。
具體例子,略,網速太慢,至今還未下好。。
參考書籍:Java程序性能優化