天天看點

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

目錄

一、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也內建了對應的工具!

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 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

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

2.1.2 界面簡介

Visual VM啟動成功!可以看到Visual VM的界面了。通過Visual VM可以看到本機運作中的所有Java應用。你會發現根本不需要在VisualVM 裡為Java應用程式注冊,它們就會自動顯示出來。甚至還可以在導航欄裡檢視到遠端的Java應用。導航欄即為Applications,其中分為Local(本地Java應用)和Remote(遠端Java應用)。

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

2.1.3 安裝插件

Visual VM有很多好用的插件,步驟如下:

      1. 點選Tools -> Plugins

      2. 推薦安裝全部插件

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 2.2 監控本地Java應用

Visual VM本身就是一個Java應用,是以打開Visual VM看到的第一個可監控應用就是Visual VM本身;可以用它熱熱身,小試下牛刀。在Visual VM可視化界面中可以監控到Visual VM本身的記憶體使用情況、線程情況、Jvm啟動參數、cpu消耗情況、垃圾回收情況等很多參數。當然如果在本地啟一個Tomcat一樣可以看到這些參數,可以友善我們在本地對JVM進行調優。但是且接如果你是在windows下起應用,如果你的Java應用是在NTFS格式的盤附上,記得加參數:-XX:+PerfBypassFileSystemCheck

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 2.2.1 使用Visual VM監測記憶體洩漏、解決記憶體溢出問題

2.2.1.1  記憶體洩露、溢出的異同

同:都會導緻應用程式運作出現問題,性能下降或挂起。

異:

1) 記憶體洩露是導緻記憶體溢出的原因之一;記憶體洩露積累起來将導緻記憶體溢出。

2) 記憶體洩露可以通過完善代碼來避免;記憶體溢出可以通過調整配置來減少發生頻率,但無法徹底避免。

2.2.1.2  監測記憶體洩漏

  • 記憶體洩漏是指程式中間動态配置設定了記憶體,但在程式結束時沒有釋放這部分記憶體,進而造成那部分記憶體不可用的情況,重新開機計算機可以解決,但也有可能再次發生記憶體洩露,記憶體洩露和硬體沒有關系,它是由軟體設計缺陷引起的。 
  • 記憶體洩漏可以分為4類:

1) 常發性記憶體洩漏。發生記憶體洩漏的代碼會被多次執行到,每次被執行的時候都會導緻一塊記憶體洩漏。

2) 偶發性記憶體洩漏。發生記憶體洩漏的代碼隻有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對于特定的環境,偶發性的也許就變成了常發性的。是以測試環境和測試方法對檢測記憶體洩漏至關重要。

3) 一次性記憶體洩漏。發生記憶體洩漏的代碼隻會被執行一次,或者由于算法上的缺陷,導緻總會有一塊僅且一塊記憶體發生洩漏。比如,在類的構造函數中配置設定記憶體,在析構函數中卻沒有釋放該記憶體,是以記憶體洩漏隻會發生一次。

4) 隐式記憶體洩漏。程式在運作過程中不停的配置設定記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡并沒有發生記憶體洩漏,因為最終程式釋放了所有申請的記憶體。但是對于一個伺服器程式,需要運作幾天,幾周甚至幾個月,不及時釋放記憶體也可能導緻最終耗盡系統的所有記憶體。是以,我們稱這類記憶體洩漏為隐式記憶體洩漏。

每隔一段時間給所監測的Java應用來一個heap dump,如下面三圖所示:

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 對比上面三個截圖,發現似乎有個東西在急速飙升,仔細一看是這個對象: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)那麼就會産生此錯誤資訊了。

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 如上圖所示,PermGen在程式運作一段時間後超出了我們指定的128MB,通過Classes視圖看到,Java在運作的同時加載了大量的類到記憶體中。PermGen會存儲Jar或者Class的描述資訊;是以在class大量增加的同時PermGen超出了我們指定的範圍。為了讓應用穩定,我們需要探尋新的PermGen範圍。檢測時段時候後(如下圖)發現PermGen在145MB左右穩定,那麼我們就得到了穩定的新參數;這樣PermGen記憶體溢出的問題得到解決。

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 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的消耗。

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

如上圖所示,used heap的折線圖呈峰狀,說明垃圾對象及時被回收了,記憶體得以釋放。如果used heap的值隻增不減說明存在記憶體洩漏了,如果超過heap size的值,會報記憶體溢出的錯誤。

2.2.1.4  如何避免記憶體洩漏、溢出

1) 盡早釋放無用對象的引用。

          好的辦法是使用臨時變量的時候,讓引用變量在退出活動域後自動設定為null,暗示垃圾收集器來收集該對象,防止發生記憶體洩露。

2) 程式進行字元串處理時,盡量避免使用String,而應使用StringBuffer。

          因為每一個String對象都會獨立占用記憶體一塊區域,如:

  1. String str = "aaa";    
  2. String str2 = "bbb";    
  3. String str3 = str + str2;    
  4. // 假如執行此次之後str , str2再不被調用,那麼它們就會在記憶體中等待GC回收;    
  5. // 假如程式中存在過多的類似情況就會出現記憶體錯誤;   

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 

八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)
八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

繼續閱讀