JVM 記憶體溢位追蹤調優與 記憶體溢位、棧溢位原因
出處1:http://www.iteye.com
寫java程式時大家一定對一下兩條異常並不陌生:
java.lang.OutOfMemoryError: Java heap spacejava.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工具進行查詢大概出現棧溢位的位置