目錄
一、jdk工具之jps(JVM Process Status Tools)命令使用
二、jdk命令之javah命令(C Header and Stub File Generator)
三、jdk工具之jstack(Java Stack Trace)
四、jdk工具之jstat命令(Java Virtual Machine Statistics Monitoring Tool)
五、jdk工具之jmap(java memory map)、 mat之四--結合mat對內存泄露的分析
六、jdk工具之jinfo命令(Java Configuration Info)
七、jdk工具之jconsole命令(Java Monitoring and Management Console)
八、jdk工具之JvisualVM、JvisualVM之二--Java程序性能分析工具Java VisualVM
九、jdk工具之jhat命令(Java Heap Analyse Tool)
十、jdk工具之Jdb命令(The Java Debugger)
十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
1.1 Visual VM簡介
VisualVM 提供在 Java 虛擬機 (Java Virutal Machine, JVM) 上運行的 Java 應用程序的詳細信息。在 VisualVM 的圖形用戶界面中,您可以方便、快捷地查看多個 Java 應用程序的相關信息。(摘自官方) 簡單說來,VisualVM是一種集成了多個JDK命令行工具的可視化工具,它能為您提供強大的分析能力。所有這些都是免費的!它囊括的命令行工具包括jstat, JConsole, jstack, jmap 和 jinfo,這些工具與JDK的標准版本是一致的。 可以使用VisualVM生成和分析海量數據、跟蹤內存泄漏、監控垃圾回收器、執行內存和CPU分析,同時它還支持在MBeans上進行瀏覽和操作。盡管VisualVM自身要在JDK6這個版本上運行,但是JDK1.4以上版本的程序它都能監控。
1.2 如何獲取VisualVM
VisualVM的一個最大好處就是,它已經在你的JDK bin目錄里了,只要你使用的是JDK1.6 Update7之后的版本。點擊一下jvisualvm.exe圖標它就可以運行了。
這里是VisualVM 的官方網站:http://visualvm.java.net/download.html,資料很全,同時提供VisualVM最近版本下載。
當然,如果你使用Myeclipse的話,該IDE也集成了對應的工具!



2.1 開啟Visual VM之旅
如果你使用的是JDK是1.6Update7之后的版本,那么Visual VM已經包含在bin目錄下了,否則需要去官方下載。
2.1.1 啟動問題
如果你在windows上使用Visual VM,需要做的只是點一下jvisualvm.exe,就能啟動它;綠色,好用。但是Visual VM所在的分區如果是NTFS格式,那么第一個問題就出現了:sun對NTFS格式的硬盤支持有問題!但可通過參數可避免,並完成啟動。步驟如下:
1. 創建一個visualvm.exe的快捷方式(或者像上文一樣,在MyEclipse中啟動)
2. 在“目標”中添加如下參數
-XX:+PerfBypassFileSystemCheck
2.1.2 界面簡介
Visual VM啟動成功!可以看到Visual VM的界面了。通過Visual VM可以看到本機運行中的所有Java應用。你會發現根本不需要在VisualVM 里為Java應用程序注冊,它們就會自動顯示出來。甚至還可以在導航欄里查看到遠程的Java應用。導航欄即為Applications,其中分為Local(本地Java應用)和Remote(遠程Java應用)。
2.1.3 安裝插件
Visual VM有很多好用的插件,步驟如下:
1. 點擊Tools -> Plugins
2. 推薦安裝全部插件
2.2 監控本地Java應用
Visual VM本身就是一個Java應用,所以打開Visual VM看到的第一個可監控應用就是Visual VM本身;可以用它熱熱身,小試下牛刀。在Visual VM可視化界面中可以監控到Visual VM本身的內存使用情況、線程情況、Jvm啟動參數、cpu消耗情況、垃圾回收情況等很多參數。當然如果在本地啟一個Tomcat一樣可以看到這些參數,可以方便我們在本地對JVM進行調優。但是且接如果你是在windows下起應用,如果你的Java應用是在NTFS格式的盤附上,記得加參數:-XX:+PerfBypassFileSystemCheck
2.2.1 使用Visual VM監測內存泄漏、解決內存溢出問題
2.2.1.1 內存泄露、溢出的異同
同:都會導致應用程序運行出現問題,性能下降或掛起。
異:
1) 內存泄露是導致內存溢出的原因之一;內存泄露積累起來將導致內存溢出。
2) 內存泄露可以通過完善代碼來避免;內存溢出可以通過調整配置來減少發生頻率,但無法徹底避免。
2.2.1.2 監測內存泄漏
-
內存泄漏是指程序中間動態分配了內存,但在程序結束時沒有釋放這部分內存,從而造成那部分內存不可用的情況,重啟計算機可以解決,但也有可能再次發生內存泄露,內存泄露和硬件沒有關系,它是由軟件設計缺陷引起的。
-
內存泄漏可以分為4類:
1) 常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。
2) 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。
3) 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由於算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。
4) 隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這里並沒有發生內存泄漏,因為最終程序釋放了所有申請的內存。但是對於一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏為隱式內存泄漏。
每隔一段時間給所監測的Java應用來一個heap dump,如下面三圖所示:
對比上面三個截圖,發現似乎有個東西在急速飆升,仔細一看是這個對象:org.eclipse.swt.graphics.Image 在第一章圖中這個還沒排的上,第二次dump已經上升到5181個,第三次就是7845個了。漲速相當快,而且和任務管理器里面看到的GDI數量增漲一致,就是它了。
問題到這兒就比較清楚了,回到代碼里面仔細一看可以發現,是某個地方反復的用圖片來創建Image對象導致的,改掉以后搞定問題。
到這里其實我想說的是,Java使用起來其實要比C++更容易導致內存泄漏。對於C++來說,每一個申請的對象都必須明確釋放,任何沒有釋放的對象都會導致memleak,這是不可饒恕的,而且這類問題已經有很多工具和方法來解決。但是到了Java里面情況就不同了,對於Java程序員來說對象都是不需要也無法主動銷毀的,所以一般的思路是:隨用隨new,用完即丟。很多對象具體的生命周期可能連寫代碼的人自己也不清楚,或者不需要清楚,只知道某個時刻垃圾收集器會去做的,不用管。但很可能某個對象在“用完即丟”的時候在另一個不容易發現的地方被保存了一個引用,那么這個對象就永遠不會被回收。更加糟糕的是整個程序從設計之初就沒有考慮過對象回收的問題,對於C++程序員來說memleak必然是一個設計錯誤,但是對Java程序員來說這只是一個疏忽,而且似乎沒有什么好的辦法來避免。今天看到的這個問題是因為GDI泄漏會造成嚴重后果才被重視,但如果僅僅是造成內存泄漏,那這個程序可能得連續跑上個十天半個月才會發現問題。最后就是,對於c++,錯誤的代碼在測試階段就可以快速的偵測出哪怕一個byte的memleak並加以改正,但是對於java程序,理論上沒有哪個工具能夠知道是不是有泄漏,因為除了作者自己以外沒有人能夠知道一個被引用的對象是不是應該被銷毀,只有通過大量的,長期的壓力測試才能發現問題,這是很危險的一件事情。
2.2.1.3 解決內存溢出問題
1、java.lang.OutOfMemoryError: PermGen space
JVM管理兩種類型的內存,堆和非堆。堆是在JVM啟動時創建;非堆是留給JVM自己用的,用來存放類的信息的。它和堆不同,運行期內GC不會釋放空間。如果web app用了大量的第三方jar或者應用有太多的class文件而恰好MaxPermSize設置較小,超出了也會導致這塊內存的占用過多造成溢出,或者tomcat熱部署時侯不會清理前面加載的環境,只會將context更改為新部署的,非堆存的內容就會越來越多。
PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域,這塊內存主要是被JVM存放Class和Meta信息的,Class在被Loader時就會被放到PermGen space中,它和存放類實例(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,所以如果你的應用中有很CLASS的話,就很可能出現PermGen space錯誤,這種錯誤常見在web服務器對JSP進行pre compile的時候。如果你的WEB APP下都用了大量的第三方jar, 其大小超過了jvm默認的大小(4M)那么就會產生此錯誤信息了。
如上圖所示,PermGen在程序運行一段時間后超出了我們指定的128MB,通過Classes視圖看到,Java在運行的同時加載了大量的類到內存中。PermGen會存儲Jar或者Class的描述信息;所以在class大量增加的同時PermGen超出了我們指定的范圍。為了讓應用穩定,我們需要探尋新的PermGen范圍。檢測時段時候后(如下圖)發現PermGen在145MB左右穩定,那么我們就得到了穩定的新參數;這樣PermGen內存溢出的問題得到解決。

2、java.lang.OutOfMemoryError: Java heap space
第一種情況是個補充,主要存在問題就是出現在這個情況中。其默認空間(即-Xms)是物理內存的1/64,最大空間(-Xmx)是物理內存的1/4。如果內存剩余不到40%,JVM就會增大堆到Xmx設置的值,內存剩余超過70%,JVM就會減小堆到Xms設置的值。所以服務器的Xmx和Xms設置一般應該設置相同避免每次GC后都要調整虛擬機堆的大小。假設物理內存無限大,那么JVM內存的最大值跟操作系統有關,一般32位機是1.5g到3g之間,而64位的就不會有限制了。
注意:如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了物理內存或者操作系統的最大限制都會引起服務器啟動不起來。
垃圾回收GC的角色,JVM調用GC的頻度還是很高的,主要兩種情況下進行垃圾回收:
一個是當應用程序線程空閑;另一個是java內存堆不足時,會不斷調用GC,若連續回收都解決不了內存堆不足的問題時,就會報out of memory錯誤。因為這個異常根據系統運行環境決定,所以無法預期它何時出現。
根據GC的機制,程序的運行會引起系統運行環境的變化,增加GC的觸發機會。
為了避免這些問題,程序的設計和編寫就應避免垃圾對象的內存占用和GC的開銷。顯示調用System.GC()只能建議JVM需要在內存中對垃圾對象進行回收,但不是必須馬上回收。一個是並不能解決內存資源耗空的局面,另外也會增加GC的消耗。
如上圖所示,used heap的折線圖呈峰狀,說明垃圾對象及時被回收了,內存得以釋放。如果used heap的值只增不減說明存在內存泄漏了,如果超過heap size的值,會報內存溢出的錯誤。
2.2.1.4 如何避免內存泄漏、溢出
1) 盡早釋放無用對象的引用。
好的辦法是使用臨時變量的時候,讓引用變量在退出活動域后自動設置為null,暗示垃圾收集器來收集該對象,防止發生內存泄露。
2) 程序進行字符串處理時,盡量避免使用String,而應使用StringBuffer。
因為每一個String對象都會獨立占用內存一塊區域,如:
-
String str = "aaa";
-
String str2 = "bbb";
-
String str3 = str + str2;
-
// 假如執行此次之后str , str2再不被調用,那么它們就會在內存中等待GC回收;
-
// 假如程序中存在過多的類似情況就會出現內存錯誤;
3) 盡量少用靜態變量。
因為靜態變量是全局的,GC不會回收。
4) 避免集中創建對象尤其是大對象,如果可以的話盡量使用流操作。
JVM會突然需要大量內存,這時會觸發GC優化系統內存環境; 一個案例如下:
很久以前,使用jspsmartUpload作文件上傳,現在運行過程中經常出現java.outofMemoryError的錯誤,用top命令看看進程使用情況,發現內存不足2M,花了很長時間,發現是jspsmartupload的問題。把jspsmartupload組件的源碼文件(class文件)反編譯成Java文件,如夢方醒:
m_totalBytes = m_request.getContentLength();
m_binArray = new byte[m_totalBytes];
變量m_totalBytes表示用戶上傳的文件的總長度,這是一個很大的數。如果用這樣大的數去聲明一個byte數組,並給數組的每個元素分配內存空間,而且m_binArray數組不能馬上被釋放,JVM的垃圾回收確實有問題,導致的結果就是內存溢出。
jspsmartUpload為什末要這樣作,有他的原因,根據RFC1867的http上傳標准,得到一個文件流,並不知道文件流的長度。設計者如果想文件的長度,只有操作servletinputstream一次才知道,因為任何流都不知道大小。只有知道文件長度了,才可以限制用戶上傳文件的長度。為了省去這個麻煩,jspsmartUpload設計者直接在內存中打開文件,判斷長度是否符合標准,符合就寫到服務器的硬盤。這樣產生內存溢出,這只是我的一個猜測而已。
所以編程的時候,不要在內存中申請大的空間,因為web服務器的內存有限,並且盡可能的使用流操作,例如
byte[] mFileBody = new byte[512];
Blob vField= rs.getBlob("FileBody");
InputStream instream=vField.getBinaryStream();
FileOutputStream fos=new FileOutputStream(saveFilePath+CFILENAME);
int b;
while( (b =instream.read(mFileBody)) != -1){
fos.write(mFileBody,0,b);
}
fos.close();
instream.close();
5) 盡量運用對象池技術以提高系統性能。
生命周期長的對象擁有生命周期短的對象時容易引發內存泄漏,例如大集合對象擁有大數據量的業務對象的時候,可以考慮分塊進行處理,然后解決一塊釋放一塊的策略。
6) 不要在經常調用的方法中創建對象,尤其是忌諱在循環中創建對象。
可以適當的使用hashtable,vector 創建一組對象容器,然后從容器中去取那些對象,而不用每次new之后又丟棄。
7) 優化配置。
1、 設置-Xms、-Xmx相等;
2、 設置NewSize、MaxNewSize相等;
3、 設置Heap size, PermGen space:
Tomcat 的配置示例:修改 %TOMCAT_HOME%/bin/catalina.bat or catalina.sh
在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:
set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m

