1. 程式人生 > >JVM 記憶體溢位追蹤調優與 記憶體溢位、棧溢位原因

JVM 記憶體溢位追蹤調優與 記憶體溢位、棧溢位原因

出處1:http://www.iteye.com

寫java程式時大家一定對一下兩條異常並不陌生: 

java.lang.OutOfMemoryError: Java heap space 
java.lang.OutOfMemoryError: PermGen space 
尤其當應用伺服器(Java容器)出現上述情況更是讓人有一種天塌下來的感覺。 

   好的編碼實踐可能會大大降低記憶體溢位的產生。 
   本文並不是寫如何規避記憶體溢位,但是我還是要介紹一下如何能夠儘量規避記憶體溢位: 
   1. 編碼規範認真執行。找幾個資深程式猿(或者整個專案組討論後)寫一個Java編碼規範,讓專案組成員儘量遵守。一目瞭然的程式碼更容易定位問題,當然也更能讓人寫出好的程式碼。 

   2. 單元測試要覆蓋所有分支與邊界條件。不要拿某種情況不會出現做藉口。有句老話說常在河邊站哪有不溼鞋(學名墨菲定律)。 
   3. 程式碼審查要走。程式碼寫完了,找資深程式猿掃掃程式碼沒有壞處。 
   4. 有條件的專案組要充分利用測試人員的能動性。 
   5. 如果專案的期望較高,就把上面的儘量、可能等詞彙改成一定要。 

   以上五條建議對非性命攸關型專案足夠了。 

下面說正題: 


   對於java.lang.OutOfMemoryError: PermGen space 這種情況更多的是靠程式猿的經驗來解決: 

   PermGen space的全稱是Permanent Generation space,是指記憶體的永久儲存區域, 這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Load時就會被放到PermGen space中, 它和存放類例項(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程式執行期對 PermGen space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen space錯誤。 


   通過上面的描述就可以得出:如果要載入的class與jar檔案大小超過-XX:MaxPermSize就有可能會產生java.lang.OutOfMemoryError: PermGen space 。 

   換句話說-XX:MaxPermSize的大小要超過class與jar的大小。通常-XX:MaxPermSize為-Xmx的1/8。 

   對於java.lang.OutOfMemoryError: Java heap space 可能定位的過程就需要折騰一翻了:

   雖然各種java虛擬機器的實現機制不一,但是heap space記憶體溢位產生的原因相同:那就是堆記憶體不夠虛擬機器分配了。 


   我對java虛擬機器的實現不感興趣,對各種虛擬機器的記憶體分配機制與gc的互動關係也不瞭解。但是我大致認為記憶體分配機制與gc是有聯絡的,也就是說記憶體不夠分配時gc肯定也釋放不了堆記憶體。從這一點出發,我們就需要找為什麼gc釋放不了堆記憶體。通常來說釋放不了是因為記憶體還在使用。對於java物件產生的堆記憶體佔用,只要其不再被繼續引用gc是能夠順利回收的(對於通過本地方法呼叫,即JNI呼叫產生記憶體洩露的情況暫不考慮)。 

   問題的關鍵就找到了,當產生heap space記憶體溢位時,堆記憶體中物件數量過多的就可能是問題的根源了。例外的情況是,程式確實需要那麼多記憶體,這時就要考慮增大堆記憶體。 

   例外的情況在本文中就不再多說了,下面介紹jdk自帶的兩個視覺化工具來定位問題。 

jdk/jconsole.exe  jdk/jvisualvm.exe 

   jconsole.exe可以檢視本地以及遠端主機上的java虛擬機器的當前狀況,這對伺服器健康檢查情況非常有用。如下圖: 


   jvisualvm.exe可以用來檢視分析記憶體轉儲檔案;也可以用其做java虛擬機器當前狀況檢視,但是jvisualvm.exe的侵入性非常強,一旦使用會嚴重影響應用效能。如下圖: 

  下面寫些程式碼來演示一下記憶體溢位的產生,堆轉儲檔案的生成,堆記憶體的分析。 

  首先建立資料持有物件類: 

package com.zas.jvm.om;

/**
 * 資料物件
 * @author zas
 */
public class DataObject {
	//資料物件ID
	private String id;
	//資料物件內容
	private String des;
	
	public DataObject(String id, String des) {
		super();
		this.id = id;
		this.des = des;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getDes() {
		return des;
	}

	public void setDes(String des) {
		this.des = des;
	}

	@Override
	public String toString() {
		return "DataObject [id=" + id + ", des=" + des + "]";
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {

	}

}


溢位演示程式碼 
package com.zas.jvm.om;

import java.util.ArrayList;
import java.util.List;

public class OutMemeryTest {
	
	List<DataObject> list = new ArrayList<DataObject>();
	
	public void testOm(){
		for (int i = 0; i < 100000; i++) {
			DataObject data = new DataObject("id&"+i, "des:"+i);
			list.add(data);
		}
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		OutMemeryTest omt = new OutMemeryTest();
		for (int i = 0; i < 2; i++) {
			omt.testOm();
		}
		System.out.println("DOne!");
		try {
			Thread.sleep(100000000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}



執行引數設定如下:-Xms64m -Xmx64m -XX:PermSize=8m -XX:MaxPermSize=8m 
-XX:-HeapDumpOnOutOfMemoryError           
見下圖: 




jvisualvm分析效果圖: 


從上圖結合程式碼明顯得出:com.zas.jvm.om.DataObject這個類的物件出了問題。 


以上是一個演示問題產生及定位過程,生產環境的問題千奇百怪需要具體問題具體分析。 

當堆記憶體巨大時可能要調整jdk\lib\visualvm\etc\visualvm.conf檔案中的-xms -xmx大小來匯入轉儲檔案。 

生產環境為linux的較多,可以藉助jdk自帶的jmap來轉儲堆記憶體檔案來分析。

-------------------------------------------------------------------------------------------------------------------------------------------------------------

java 記憶體溢位 棧溢位的原因與排查方法

 1、 記憶體溢位的原因是什麼?

記憶體溢位是由於沒被引用的物件(垃圾)過多造成JVM沒有及時回收,造成的記憶體溢位。如果出現這種現象可行程式碼排查:

    一)是否App中的類中和引用變數過多使用了Static修飾 如public staitc Student s;在類中的屬性中使用 static修飾的最好只用基本型別或字串。如public static int i = 0; //public static String str;

  二)是否App中使用了大量的遞迴或無限遞迴(遞迴中用到了大量的建新的物件)

  三)是否App中使用了大量迴圈或死迴圈(迴圈中用到了大量的新建的物件)

  四)檢查App中是否使用了向資料庫查詢所有記錄的方法。即一次性全部查詢的方法,如果資料量超過10萬多條了,就可能會造成記憶體溢位。所以在查詢時應採用“分頁查詢”。

  五)檢查是否有陣列,List,Map中存放的是物件的引用而不是物件,因為這些引用會讓對應的物件不能被釋放。會大量儲存在記憶體中。

  六)檢查是否使用了“非字面量字串進行+”的操作。因為String類的內容是不可變的,每次執行"+"就會產生新的物件,如果過多會造成新String物件過多,從而導致JVM沒有及時回收而出現記憶體溢位。

   如String s1 = "My name";

       String s2 = "is";

       String s3 = "xuwei";

      String str = s1 + s2 + s3 +.........;這是會容易造成記憶體溢位的

     但是String str =  "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是這種就不會造成記憶體溢位。因為這是”字面量字串“,在執行"+"時就會在編譯期間執行好。不會按照JVM來執行的。

   在使用String,StringBuffer,StringBuilder時,如果是字面量字串進行"+"時,應選用String效能更好;如果是String類進行"+"時,在不考慮執行緒安全時,應選用StringBuilder效能更好。

  七)使用 DDMS工具進行查詢記憶體溢位的大概位置

2、棧溢位的原因

一)、是否有遞迴呼叫

    二)、是否有大量迴圈或死迴圈

   三)、全域性變數是否過多

   四)、 陣列、List、map資料是否過大

   五)使用DDMS工具進行查詢大概出現棧溢位的位置