1. 程式人生 > >java記憶體洩漏分類及避免

java記憶體洩漏分類及避免

要點

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

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

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

3) 一次性記憶體洩漏。發生記憶體洩漏的程式碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊僅且一塊記憶體發生洩漏。比如,在類的建構函式中分配記憶體,在解構函式中卻沒有釋放該記憶體,所以記憶體洩漏只會發生一次。

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

  • 記憶體溢位型別

1) java.lang.OutOfMemoryError: PermGen space

PermGen space 的全稱是 Permanent Generation space, 是指記憶體的永久儲存區域。這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到PermGenspace中,它和存放類例項(Instance)的Heap區域不同,GC不會在主程式執行期對PermGen space進行清理。

JVM由XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;

JVM由XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。

該錯誤常見場合:

a) 應用中有很多Class,web伺服器對JSP進行precompile時。

b) Webapp下用了大量的第三方jar,其大小超過了JVM預設的大小(4M)時。

2) java.lang.OutOfMemoryError:Java heap space

在JVM中如果98%的時間是用於GC且可用的Heap size 不足2%的時候將丟擲此異常資訊。

JVM初始分配的記憶體由-Xms指定,預設是實體記憶體的1/64; 

JVM最大分配的記憶體由-Xmx指定,預設是實體記憶體的1/4。 

JVM記憶體的最大值跟作業系統有很大的關係。32位處理器雖然可控記憶體空間有4GB,但是具體的作業系統會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統下為1.5G-2G,Linux系統下為2G-3G),而64bit以上的處理器就不會有限制了。

注意:如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了實體記憶體或者作業系統的最大限制都會引起伺服器啟動不起來。

該錯誤常見場合:

a) Web上傳檔案時。

b) 開啟大型檔案或從資料庫中一次取了太多的資料。 
 

相關問題

1. Q: Java中會存在記憶體洩漏嗎?    A:  Java中也存在記憶體洩露。當被分配的物件可達但已無用(未對作廢資料記憶體單元的引用置null)即會引起。

         如:

Java程式碼

  1. Vector v=new Vector(10);    
  2. for (int i=1;i<100; i ) {    
  3.     Object o=new Object();    
  4.     v.add(o);    
  5.     o=null;    
  6. }    
  7. // 此時,所有的Object物件都沒有被釋放,因為變數v引用這些物件。    
  8. // 物件加入到Vector後,還必須從Vector中刪除,最簡單釋放方法就是將Vector物件設定為null。   

Vector v=new Vector(10); for(int i=1;i<100; i ) { Object o=new Object(); v.add(o); o=null; } // 此時,所有的Object物件都沒有被釋放,因為變數v引用這些物件。 // 物件加入到Vector後,還必須從Vector中刪除,最簡單釋放方法就是將Vector物件設定為null。

2. Q: 記憶體洩露、溢位的異同? 

     A: 同:都會導致應用程式執行出現問題,效能下降或掛起。

         異:

         1) 記憶體洩露是導致記憶體溢位的原因之一;記憶體洩露積累起來將導致記憶體溢位。

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

3. 如何檢測記憶體洩露? 

A: 可以通過一些效能監測分析工具,如 JProfiler、Optimizeit Profiler。

4. Q: 如何避免記憶體洩露、溢位?
    
A: 1) 儘早釋放無用物件的引用。

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

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

          因為每一個String物件都會獨立佔用記憶體一塊區域,如:

Java程式碼

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

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

         3) 儘量少用靜態變數。

         因為靜態變數是全域性的,GC不會回收。

         4) 避免集中建立物件尤其是大物件,如果可以的話儘量使用流操作。

         JVM會突然需要大量記憶體,這時會觸發GC優化系統記憶體環境; 一個案例如下: 

Java程式碼

  1. // 使用jspsmartUpload作檔案上傳,執行過程中經常出現java.outofMemoryError的錯誤,    
  2. // 檢查之後發現問題:元件裡的程式碼    
  3. m_totalBytes = m_request.getContentLength();    
  4. m_binArray = new byte[m_totalBytes];    
  5. // totalBytes這個變數得到的數極大,導致該陣列分配了很多記憶體空間,而且該陣列不能及時釋放。    
  6. // 解決辦法只能換一種更合適的辦法,至少是不會引發outofMemoryError的方式解決。    
  7. // 參考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747   

// 使用jspsmartUpload作檔案上傳,執行過程中經常出現java.outofMemoryError的錯誤, // 檢查之後發現問題:元件裡的程式碼 m_totalBytes = m_request.getContentLength(); m_binArray = newbyte[m_totalBytes]; // totalBytes這個變數得到的數極大,導致該陣列分配了很多記憶體空間,而且該陣列不能及時釋放。 // 解決辦法只能換一種更合適的辦法,至少是不會引發outofMemoryError的方式解決。 // 參考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747

        5) 儘量運用物件池技術以提高系統性能。

         生命週期長的物件擁有生命週期短的物件時容易引發記憶體洩漏,例如大集合物件擁有大資料量的業務物件的時候,可以考慮分塊進行處理,然後解決一塊釋放一塊的策略。

         6) 不要在經常呼叫的方法中建立物件,尤其是忌諱在迴圈中建立物件。

         可以適當的使用hashtable,vector 建立一組物件容器,然後從容器中去取那些物件,而不用每次new之後又丟棄。

         7) 優化配置。

5. Q: 記憶體溢位的解決方案?      A: 一是從程式碼層面進行優化完善,儘量避免該情況發生;

         二是調整優化伺服器配置: 

         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"”上面加入以下行:

Cmd程式碼

  1. set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m  

-----------------------------/////////////////////////////

記憶體溢位是由於jvm虛擬記憶體不夠!
而洩漏則是另種概念!1

下面實在摘抄的內容!!

下面,我們就可以描述什麼是記憶體洩漏。在Java中,記憶體洩漏就是存在一些被分配的物件,這些物件有下面兩個特點,首先,這些物件是可達的,即在有向圖中,存在通路可以與其相連;其次,這些物件是無用的,即程式以後不會再使用這些物件。如果物件滿足這兩個條件,這些物件就可以判定為Java中的記憶體洩漏,這些物件不會被GC所回收,然而它卻佔用記憶體。

在C++中,記憶體洩漏的範圍更大一些。有些物件被分配了記憶體空間,然後卻不可達,由於C++中沒有GC,這些記憶體將永遠收不回來。在Java中,這些不可達的物件都由GC負責回收,因此程式設計師不需要考慮這部分的記憶體洩露。

通過分析,我們得知,對於C++,程式設計師需要自己管理邊和頂點,而對於Java程式設計師只需要管理邊就可以了(不需要管理頂點的釋放)。通過這種方式,Java提高了程式設計的效率。


因此,通過以上分析,我們知道在Java中也有記憶體洩漏,但範圍比C++要小一些。因為Java從語言上保證,任何物件都是可達的,所有的不可達物件都由GC管理。

對於程式設計師來說,GC基本是透明的,不可見的。雖然,我們只有幾個函式可以訪問GC,例如執行GC的函式System.gc(),但是根據Java語言規範定義, 該函式不保證JVM的垃圾收集器一定會執行。因為,不同的JVM實現者可能使用不同的演算法管理GC。通常,GC的執行緒的優先級別較低。JVM呼叫GC的策略也有很多種,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。除非在一些特定的場合,GC的執行影響應用程式的效能,例如對於基於Web的實時系統,如網路遊戲等,使用者不希望GC突然中斷應用程式執行而進行垃圾回收,那麼我們需要調整GC的引數,讓GC能夠通過平緩的方式釋放記憶體,例如將垃圾回收分解為一系列的小步驟執行,Sun提供的HotSpot JVM就支援這一特性。

下面給出了一個簡單的記憶體洩露的例子。在這個例子中,我們迴圈申請Object物件,並將所申請的物件放入一個Vector中,如果我們僅僅釋放引用本身,那麼Vector仍然引用該物件,所以這個物件對GC來說是不可回收的。因此,如果物件加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector物件設定為null。

Java程式碼  

  1. Vector v=new Vector(10);   
  2. for (int i=1;i<100; i++)   
  3. {   
  4.     Object o=new Object();   
  5.     v.add(o);   
  6.     o=null;    
  7. }   

Vector v=new Vector(10);

for (int i=1;i<100; i++)

{

  Object o=new Object();

  v.add(o);

  o=null; 

}


//此時,所有的Object物件都沒有被釋放,因為變數v引用這些物件

怎樣解決記憶體溢位

 一、記憶體溢位型別

  1、java.lang.OutOfMemoryError:PermGen space

  JVM管理兩種型別的記憶體,堆和非堆。堆是給開發人員用的上面說的就是,是在JVM啟動時建立;非堆是留給JVM自己用的,用來存放類的資訊的。它和堆不同,執行期內GC不會釋放空間。如果web app用了大量的第三方jar或者應用有太多的class檔案而恰好MaxPermSize設定較小,超出了也會導致這塊記憶體的佔用過多造成溢位,或者tomcat熱部署時侯不會清理前面載入的環境,只會將context更改為新部署的,非堆存的內容就會越來越多。

  PermGen space的全稱是PermanentGeneration space,是指記憶體的永久儲存區域,這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到PermGen space中,它和存放類例項(Instance)的Heap區域不同,GC(GarbageCollection)不會在主程式執行期對PermGen space進行清理,所以如果你的應用中有很CLASS的話,就很可能出現PermGenspace錯誤,這種錯誤常見在web伺服器對JSP進行pre compile的時候。如果你的WEB APP下都用了大量的第三方jar, 其大小超過了jvm預設的大小(4M)那麼就會產生此錯誤資訊了。

  一個最佳的配置例子:(經過本人驗證,自從用此配置之後,再未出現過tomcat死掉的情況)

  set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M-XX:MaxNewSize=256m -XX:MaxPermSize=256m

  2、java.lang.OutOfMemoryError:Javaheap 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的消耗。

  二、JVM記憶體區域組成

  簡單的說java中的堆和棧

  java把記憶體分兩種:一種是棧記憶體,另一種是堆記憶體

  1。在函式中定義的基本型別變數和物件的引用變數都在函式的棧記憶體中分配;

  2。堆記憶體用來存放由new建立的物件和陣列

  在函式(程式碼塊)中定義一個變數時,java就在棧中為這個變數分配記憶體空間,當超過變數的作用域後,java會自動釋放掉為該變數所分配的記憶體空間;在堆中分配的記憶體由java虛擬機器的自動垃圾回收器來管理

  堆的優勢是可以動態分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在執行時動態分配記憶體的。缺點就是要在執行時動態分配記憶體,存取速度較慢;

  棧的優勢是存取速度比堆要快,缺點是存在棧中的資料大小與生存期必須是確定的無靈活性。

  java堆分為三個區:New、Old和Permanent

  GC有兩個執行緒:

  新建立的物件被分配到New區,當該區被填滿時會被GC輔助執行緒移到Old區,當Old區也填滿了會觸發GC主執行緒遍歷堆記憶體裡的所有物件。Old區的大小等於Xmx減去-Xmn

  java棧存放

  棧調整:引數有+UseDefaultStackSize -Xss256K,表示每個執行緒可申請256k的棧空間

  每個執行緒都有他自己的Stack

  三、JVM如何設定虛擬記憶體

  提示:在JVM中如果98%的時間是用於GC且可用的Heap size 不足2%的時候將丟擲此異常資訊。

  提示:Heap Size 最大不要超過可用實體記憶體的80%,一般的要將-Xms和-Xmx選項設定為相同,而-Xmn為1/4的-Xmx值。

  提示:JVM初始分配的記憶體由-Xms指定,預設是實體記憶體的1/64;JVM最大分配的記憶體由-Xmx指定,預設是實體記憶體的1/4。

  預設空餘堆記憶體小於40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆記憶體大於70%時,JVM會減少堆直到-Xms的最小限制。因此伺服器一般設定-Xms、-Xmx相等以避免在每次GC 後調整堆的大小。

  提示:假設實體記憶體無限大的話,JVM記憶體的最大值跟作業系統有很大的關係。

  簡單的說就32位處理器雖然可控記憶體空間有4GB,但是具體的作業系統會給一個限制,

  這個限制一般是2GB-3GB(一般來說Windows系統下為1.5G-2G,Linux系統下為2G-3G),而64bit以上的處理器就不會有限制了

  提示:注意:如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了實體記憶體或者作業系統的最大限制都會引起伺服器啟動不起來。

  提示:設定NewSize、MaxNewSize相等,"new"的大小最好不要大於"old"的一半,原因是old區如果不夠大會頻繁的觸發"主" GC ,大大降低了效能

  JVM使用-XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;

  由XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。

  解決方法:手動設定Heap size

  修改TOMCAT_HOME/bin/catalina.bat

  在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面加入以下行:

  JAVA_OPTS="-server -Xms800m -Xmx800m -XX:MaxNewSize=256m"

  四、效能檢查工具使用

  定位記憶體洩漏:

  JProfiler工具主要用於檢查和跟蹤系統(限於Java開發的)的效能。JProfiler可以通過時時的監控系統的記憶體使用情況,隨時監視垃圾回收,執行緒執行狀況等手段,從而很好的監視JVM執行情況及其效能。

  1. 應用服務器記憶體長期不合理佔用,記憶體經常處於高位佔用,很難回收到低位;

  2. 應用伺服器極為不穩定,幾乎每兩天重新啟動一次,有時甚至每天重新啟動一次;

  3. 應用伺服器經常做Full GC(GarbageCollection),而且時間很長,大約需要30-40秒,應用伺服器在做Full GC的時候是不響應客戶的交易請求的,非常影響系統性能。

  因為開發環境和產品環境會有不同,導致該問題發生有時會在產品環境中發生,通常可以使用工具跟蹤系統的記憶體使用情況,在有些個別情況下或許某個時刻確實是使用了大量記憶體導致out of memory,這時應繼續跟蹤看接下來是否會有下降,

  如果一直居高不下這肯定就因為程式的原因導致記憶體洩漏。

  五、不健壯程式碼的特徵及解決辦法

  1、儘早釋放無用物件的引用。好的辦法是使用臨時變數的時候,讓引用變數在退出活動域後,自動設定為null,暗示垃圾收集器來收集該物件,防止發生記憶體洩露。

  對於仍然有指標指向的例項,jvm就不會回收該資源,因為垃圾回收會將值為null的物件作為垃圾,提高GC回收機制效率;

  2、我們的程式裡不可避免大量使用字串處理,避免使用String,應大量使用StringBuffer,每一個String物件都得獨立佔用記憶體一塊區域;

  String str = "aaa";

  String str2 = "bbb";

  String str3 = str + str2;//假如執行此次之後str ,str2以後再不被呼叫,那它就會被放在記憶體中等待Java的gc去回收,程式內過多的出現這樣的情況就會報上面的那個錯誤,建議在使用字串時能使用StringBuffer就不要用String,這樣可以省不少開銷;

  3、儘量少用靜態變數,因為靜態變數是全域性的,GC不會回收的;

  4、避免集中建立物件尤其是大物件,JVM會突然需要大量記憶體,這時必然會觸發GC優化系統記憶體環境;顯示的宣告陣列空間,而且申請數量還極大。

  這是一個案例想定供大家警戒

  使用jspsmartUpload作檔案上傳,執行過程中經常出現java.outofMemoryError的錯誤,

  檢查之後發現問題:元件裡的程式碼

  m_totalBytes = m_request.getContentLength();

  m_binArray = new byte[m_totalBytes];

  問題原因是totalBytes這個變數得到的數極大,導致該陣列分配了很多記憶體空間,而且該陣列不能及時釋放。解決辦法只能換一種更合適的辦法,至少是不會引發outofMemoryError的方式解決。參考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747

  5、儘量運用物件池技術以提高系統性能;生命週期長的物件擁有生命週期短的物件時容易引發記憶體洩漏,例如大集合物件擁有大資料量的業務物件的時候,可以考慮分塊進行處理,然後解決一塊釋放一塊的策略。

  6、不要在經常呼叫的方法中建立物件,尤其是忌諱在迴圈中建立物件。可以適當的使用hashtable,vector 建立一組物件容器,然後從容器中去取那些物件,而不用每次new之後又丟棄

  7、一般都是發生在開啟大型檔案或跟資料庫一次拿了太多的資料,造成 Out OfMemory Error 的狀況,這時就大概要計算一下資料量的最大值是多少,並且設定所需最小及最大的記憶體空間值。