java内存溢出 栈溢出的原因与排查方法
java內存溢出 原因與排查方法
1、 內存溢出的原因是什么?
內存溢出是由于沒被引用的對象(垃圾)過多造成JVM沒有及時回收,導致剩余的內存不夠用,造成的內存溢出。如果出現這種現象可行代碼排查:
一)是否應用中的類中和引用變量過多使用了Static修飾 如public staitc Student s;在類中的屬性中使用 static修飾的最好只用基本類型或字符串。如public static int i = 0; //public static String str;
二)是否 應用 中使用了大量的遞歸或無限遞歸(遞歸中用到了大量的建新的對象)
三)是否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性能更好。
public class Test { public void testHeap(){ for(;;){ //死循環一直創建對象,堆溢出ArrayList list = new ArrayList (2000); } } int num=1; public void testStack(){ //無出口的遞歸調用,棧溢出num++; this.testStack(); } public static void main(String[] args){ Test t = new Test (); t.testHeap(); t.testStack(); } }?
2、棧溢出的原因
?一)、是否有遞歸調用
二)、是否有大量循環或死循環
三)、全局變量是否過多
四)、 數組、List、map數據是否過大
五)使用DDMS工具進行查找大概出現棧溢出的位置
后續持續更新 請看到的及時補充到評論區……
下面是摘自掘金中的一篇文章,在項目過程中或多或少遇到過,由于本人不想再一一做測試用例,就摘錄過來了,最后附帶地址鏈接 ,可以方便大家去看原文(尊重原著)
?
JVM系列之實戰內存溢出異常
實戰內存溢出異常
大家好,相信大部分Javaer在code時經常會遇到本地代碼運行正常,但在生產環境偶爾會莫名其妙的報一些關于內存的異常,StackOverFlowError,OutOfMemoryError異常是最常見的。今天就基于上篇文章JVM系列之Java內存結構詳解講解的各個內存區域重點實戰分析下內存溢出的情況。在此之前,我還是想多余累贅一些其他關于對象的問題,具體內容如下:
文章結構
對象的創建過程
對象的內存布局
對象的訪問定位
實戰內存異常
1 . 對象的創建過程
關于對象的創建,第一反應是new關鍵字,那么本文就主要講解new關鍵字創建對象的過程。
Student stu =new Student("張三","18");就拿上面這句代碼來說,虛擬機首先會去檢查Student這個類有沒有被加載,如果沒有,首先去加載這個類到方法區,然后根據加載的Class類對象創建stu實例對象,需要注意的是,stu對象所需的內存大小在Student類加載完成后便可完全確定。內存分配完成后,虛擬機需要將分配到的內存空間的實例數據部分初始化為零值,這也就是為什么我們在編寫Java代碼時創建一個變量不需要初始化。緊接著,虛擬機會對對象的對象頭進行必要的設置,如這個對象屬于哪個類,如何找到類的元數據(Class對象),對象的鎖信息,GC分代年齡等。設置完對象頭信息后,調用類的構造函數。
其實講實話,虛擬機創建對象的過程遠不止這么簡單,我這里只是把大致的脈絡講解了一下,方便大家理解。
2 . 對象的內存布局
剛剛提到的實例數據,對象頭,有些小伙伴也許有點陌生,這一小節就詳細講解一下對象的內存布局,對象創建完成后大致可以分為以下幾個部分:
對象頭
實例數據
對齊填充
對象頭: 對象頭中包含了對象運行時一些必要的信息,如GC分代信息,鎖信息,哈希碼,指向Class類元信息的指針等,其中對Javaer比較有用的是鎖信息與指向Class對象的指針,關于鎖信息,后期有機會講解并發編程JUC時再擴展,關于指向Class對象的指針其實很好理解。比如上面那個Student的例子,當我們拿到stu對象時,調用Class stuClass=stu.getClass();的時候,其實就是根據這個指針去拿到了stu對象所屬的Student類在方法區存放的Class類對象。雖然說的有點拗口,但這句話我反復琢磨了好幾遍,應該是說清楚了。
實例數據:實例數據部分是對象真正存儲的有效信息,就是程序代碼中所定義的各種類型的字段內容。
對齊填充:虛擬機規范要求對象大小必須是8字節的整數倍。對齊填充其實就是來補全對象大小的。
3 . 對象的訪問定位
談到對象的訪問,還拿上面學生的例子來說,當我們拿到stu對象時,直接調用stu.getName();時,其實就完成了對對象的訪問。但這里要累贅說一下的是,stu雖然通常被認為是一個對象,其實準確來說是不準確的,stu只是一個變量,變量里存儲的是指向對象的指針,(如果干過C或者C++的小伙伴應該比較清楚指針這個概念),當我們調用stu.getName()時,虛擬機會根據指針找到堆里面的對象然后拿到實例數據name.需要注意的是,當我們調用stu.getClass()時,虛擬機會首先根據stu指針定位到堆里面的對象,然后根據對象頭里面存儲的指向Class類元信息的指針再次到方法區拿到Class對象,進行了兩次指針尋找。具體講解圖如下:
?
4 .實戰內存異常
內存異常是我們工作當中經常會遇到問題,但如果僅僅會通過加大內存參數來解決問題顯然是不夠的,應該通過一定的手段定位問題,到底是因為參數問題,還是程序問題(無限創建,內存泄露)。定位問題后才能采取合適的解決方案,而不是一內存溢出就查找相關參數加大。
概念:
內存泄露:代碼中的某個對象本應該被虛擬機回收,但因為擁有GCRoot引用而沒有被回收。關于GCRoot概念,下一篇文章講解。
內存溢出: 虛擬機由于堆中擁有太多不可回收對象沒有回收,導致無法繼續創建新對象。
在分析問題之前先給大家講一講排查內存溢出問題的方法,內存溢出時JVM虛擬機會退出,那么我們怎么知道JVM運行時的各種信息呢,Dump機制會幫助我們,可以通過加上VM參數-XX:+HeapDumpOnOutOfMemoryError讓虛擬機在出現內存溢出異常時生成dump文件,然后通過外部工具(作者使用的是VisualVM)來具體分析異常的原因。
下面從以下幾個方面來配合代碼實戰演示內存溢出及如何定位:
Java堆內存異常
Java棧內存異常
方法區內存異常
Java堆內存異常
/** VM Args: //這兩個參數保證了堆中的可分配內存固定為20M -Xms20m -Xmx20m //文件生成的位置,則生成在桌面的一個目錄 -XX:+HeapDumpOnOutOfMemoryError //文件生成的位置,則生成在桌面的一個目錄 //文件生成的位置,則生成在桌面的一個目錄 -XX:HeapDumpPath=/Users/zdy/Desktop/dump/ */ public class HeapOOM {//創建一個內部類用于創建對象使用static class OOMObject {}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();//無限創建對象,在堆中while (true) {list.add(new OOMObject());}} }Run起來代碼后爆出異常如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/zdy/Desktop/dump/java_pid1099.hprof ...
可以看到生成了dump文件到指定目錄。并且爆出了OutOfMemoryError,還告訴了你是哪一片區域出的問題:heap space
打開VisualVM工具導入對應的heapDump文件(如何使用請讀者自行查閱相關資料),相應的說明見圖:
分析dump文件后,我們可以知道,OOMObject這個類創建了810326個實例。所以它能不溢出嗎?接下來就在代碼里找這個類在哪new的。排查問題。(我們的樣例代碼就不用排查了,While循環太兇猛了)
Java棧內存異常
老實說,在棧中出現異常(StackOverFlowError)的概率很小,小到和去蘋果專賣店買手機,買回來后發現是Android系統的概率是一樣的。因為作者確實沒有在生產環境中遇到過,除了自己作死寫樣例代碼測試。先說一下異常出現的情況,前面講到過,方法調用的過程就是方法幀進虛擬機棧和出虛擬機棧的過程,那么有兩種情況可以導致StackOverFlowError,當一個方法幀(比如需要2M內存)進入到虛擬機棧(比如還剩下1M內存)的時候,就會報出StackOverFlow.這里先說一個概念,棧深度:指目前虛擬機棧中沒有出棧的方法幀。虛擬機棧容量通過參數-Xss來控制,下面通過一段代碼,把棧容量人為的調小一點,然后通過遞歸調用觸發異常。
/** * VM Args: //設置棧容量為160K,默認1M -Xss160k */ public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;//遞歸調用,觸發異常stackLeak();}public static void main(String[] args) throws Throwable {JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("stack length:" + oom.stackLength);throw e;}} }結果如下:
stack length:751
Exception in thread "main" java.lang.StackOverflowError
可以看到,遞歸調用了751次,棧容量不夠用了。
默認的棧容量在正常的方法調用時,棧深度可以達到1000-2000深度,所以,一般的遞歸是可以承受的住的。如果你的代碼出現了StackOverflowError,首先檢查代碼,而不是改參數。
這里順帶提一下,很多人在做多線程開發時,當創建很多線程時,容易出現OOM(OutOfMemoryError),
這時可以通過具體情況,減少最大堆容量,或者棧容量來解決問題,這是為什么呢。請看下面的公式:
線程數 * (最大棧容量) + 最大堆值 + 其他內存(忽略不計或者一般不改動) = 機器最大內存
當線程數比較多時,且無法通過業務上削減線程數,那么再不換機器的情況下,你只能把最大棧容量設置小一點,或者把最大堆值設置小一點。
方法區內存異常
寫到這里時,作者本來想寫一個無限創建動態代理對象的例子來演示方法區溢出,避開談論JDK7與JDK8的內存區域變更的過渡,但細想一想,還是把這一塊從始致終的說清楚。在上一篇文章中JVM系列之Java內存結構詳解講到方法區時提到,JDK7環境下方法區包括了(運行時常量池),其實這么說是不準確的。因為從JDK7開始,HotSpot團隊就想到開始去"永久代",大家首先明確一個概念,方法區和"永久代"(PermGen space)是兩個概念,方法區是JVM虛擬機規范,任何虛擬機實現(J9等)都不能少這個區間,而"永久代"只是HotSpot對方法區的一個實現。為了把知識點列清楚,我還是才用列表的形式:
JDK7之前(包括JDK7)擁有"永久代"(PermGen space),用來實現方法區。但在JDK7中已經逐漸在實現中把永久代中把很多東西移了出來,比如:符號引用(Symbols)轉移到了native heap,運行時常量池(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap。
所以這就是為什么我說上一篇文章中說方法區中包含運行時常量池是不正確的,因為已經移動到了java heap;
在JDK7之前(包括7)可以通過-XX:PermSize -XX:MaxPermSize來控制永久代的大小。
JDK8正式去除"永久代",換成Metaspace(元空間)作為JVM虛擬機規范中方法區的實現。
元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但仍可以通過參數控制:-XX:MetaspaceSize與-XX:MaxMetaspaceSize來控制大小。
下面作者還是通過一段代碼,來不停的創建Class對象,在JDK8中可以看到metaSpace內存溢出:
在JDK8的環境下將報出異常:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
這是因為在調用CGLib的創建代理時會生成動態代理類,即Class對象到Metaspace,所以While一下就出異常了。
提醒一下:雖然我們日常叫"堆Dump",但是dump技術不僅僅是對于"堆"區域才有效,而是針對OOM的,也就是說不管什么區域,凡是能夠報出OOM錯誤的,都可以使用dump技術生成dump文件來分析。
在經常動態生成大量Class的應用中,需要特別注意類的回收狀況,這類場景除了例子中的CGLib技術,常見的還有,大量JSP,反射,OSGI等。需要特別注意,當出現此類異常,應該知道是哪里出了問題,然后看是調整參數,還是在代碼層面優化。
附加-直接內存異常
直接內存異常非常少見,而且機制很特殊,因為直接內存不是直接向操作系統分配內存,而且通過計算得到的內存不夠而手動拋出異常,所以當你發現你的dump文件很小,而且沒有明顯異常,只是告訴你OOM,你就可以考慮下你代碼里面是不是直接或者間接使用了NIO而導致直接內存溢出。
?
?
原文地址https://my.oschina.net/u/2401092/blog/1621850
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的java内存溢出 栈溢出的原因与排查方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序江湖:第十八章 察颜观色的伙伴
- 下一篇: 分享:笔记本电脑有杂音解决技巧