如果大家有遇到過Java內存泄露問題,而且親自動手去定位和分析經歷的同學來講,獲取Java的堆內信息對了內存使用情況的問題分析和定位是非常有幫助了。例如我們常用的MAT工具,可以較方便的讓我們定位程序中內存的使用情況,是哪塊導致了內存的泄露等。
但由于傳統的分析過程比較麻煩,需要使用Jdk的jmap(Java Memory Map)命令把heap內存dump到一個文件,然后用MAT進行分析。所以本文介紹一種方法可以實現在線查看heap內存的使用情況,并附上源碼實現,希望對大家有幫助。由于目前調研中只找到了Sun JDK6以及以上版本的實現,所以目前該方案只支持Sun JDK6或以上。如果其他同學有其它版本的JDK實現分享,歡迎一起交流。
整體實現思路如下:
1. JDK6中在tools.jar類庫里有一個com.sun.tools.attach.VirtualMachine類,該類可以獲得JVM虛擬機的相關控制權限。
2. 利用getPids.exe或其它工具獲取需要監控的JVM 的pid進程號信息
3. 利用反射調用VirtualMachine的attach方法,獲取VirtualMachine的實例對象
4. 復用反射調用VirtualMachine實例的heapHisto方法,參數為 –all, 可獲到JVM的堆內存信息
5. 最后解析heapHisto方法返回的輸入流,讀取內存數據即可獲得當前JVM的堆內存數據
下面帖出的主要代碼來說明各步驟具體實現方法:
l JDK6中在tools.jar類庫里有一個com.sun.tools.attach.VirtualMachine類,該類可以獲得JVM虛擬機的相關控制權限。
private static Class<?> findVirtualMachineClass() throws ClassNotFoundException, MalformedURLException { // JVM 虛擬機操作類 final String virtualMachineClassName = "com.sun.tools.attach.VirtualMachine"; try { return Class.forName(virtualMachineClassName); } catch (final ClassNotFoundException e) { // exception ignored, try looking else where File file = new File(System.getProperty("java.home")); if ("jre".equalsIgnoreCase(file.getName())) { file = file.getParentFile(); } //直接從JDK的 lib目錄下加載 tools.jar類庫 final String[] defaultToolsLocation = { "lib", "tools.jar" }; for (final String name : defaultToolsLocation) { file = new File(file, name); } final URL[] urls = { file.toURI().toURL() }; final ClassLoader cl = URLClassLoader.newInstance(urls); //再次嘗試反射查詢 JVM虛擬機操作類 return Class.forName(virtualMachineClassName, true, cl); } } |
l 利用反射調用VirtualMachine的attach方法,獲取VirtualMachine的實例對象
本過程相對比較簡單,獲取VirtualMachine的類后,根據反射類,查詢attach方法
//獲取JVM 虛擬機操作類后 final Class<?> virtualMachineClass = findVirtualMachineClass(); //根據反射查詢 attach方法,參數為String類型 final Method attachMethod = virtualMachineClass.getMethod("attach", String.class); //通過 getpids.exe工具獲取當前JVM進程號 final String pid = PID.getPID(); try { //通過反射調用attache方法 jvmVirtualMachine = invoke(attachMethod, null, pid); } finally { enabled = jvmVirtualMachine != null; } |
l 復用反射調用VirtualMachine實例的heapHisto方法,參數為 –all, 可獲到JVM的堆內存信息
本過程也是利用反射調用heapHisto方法,實現的代碼如下:
final Class<?> virtualMachineClass = getJvmVirtualMachine().getClass(); //反射調用 heapHisto方法,參數為 -all final Method heapHistoMethod = virtualMachineClass.getMethod("heapHisto", Object[].class); //該方面返回值為InputStream return (InputStream) invoke(heapHistoMethod, getJvmVirtualMachine(), new Object[] { new Object[] { "-all" } }); |
l 最后解析heapHisto方法返回的輸入流,讀取內存數據即可獲得當前JVM的堆內存數據, 通過該方法返回的一個文本內容。
Input Stream取出的結果(文本內容)示例如下:
num #instances #bytes class name ---------------------------------------------- 1: 14948 1892768 [C 2: 958 567568 [B 3: 1870 215584 <symbolKlass> 4: 7366 176784 java.lang.String 5: 132 88104 [I 6: 841 86360 <constMethodKlass> 7: 841 67672 <methodKlass> 8: 968 61152 [Ljava.lang.Object; 9: 101 48152 <constantPoolKlass> 10: 2593 41488 java.lang.StringBuilder 11: 101 40600 <instanceKlassKlass> 12: 952 30464 java.util.TreeMap$Entry 13: 79 25112 <constantPoolCacheKlass> 14: 310 22280 [S 15: 746 17904 sun.jvmstat.perfdata.monitor.AliasFileParser$Token 16: 367 17616 java.nio.HeapCharBuffer
Total 39840 3645336
null
|
因為內容太長,只截取了部分
因為讀取的是文本內容,而且格式是完全固定的,所以大家可以直接解析里面的內容。主要是后面三例,一個是實現的個數,一個是實際的數據內容,最后一個是實例關聯的類名稱.
Good Luck!
Yours Matthew!