jvm中除了程序計數器,其餘的區域都有可能會發生內存溢出java
當程序須要申請內存的時候,因爲沒有足夠的內存,此時就會拋出OutOfMemoryError,這就是內存溢出linux
內存泄漏是因爲使用不當,把一部份內存「丟掉了」,致使這部份內存不可用。
當在堆中建立了對象,後來沒有使用這個對象了,又沒有把整個對象的相關引用設爲null。此時垃圾收集器會認爲這個對象是須要的,就不會清理這部份內存。這就會致使這部份內存不可用。
因此內存泄漏會致使可用的內存減小,進而會致使內存溢出。算法
下面爲了說明溢出的情景,會執行一些實例代碼,同時須要給jvm指定參數服務器
-XX:MaxPermSize=size 永生代最大容量jvm
堆是存放對象的地方,那麼只要在堆中瘋狂的建立對象,那麼堆就會發生內存溢出。工具
下面作一個堆溢出的實驗
執行這段代碼的時候,要給jvm指定參數優化
//jvm參數:-Xms20m -Xmx20m public class HeapOOMTest { public static void main(String[] args){ LinkedList<HeapOOMTest> l=new LinkedList<HeapOOMTest>();//做爲GC Root while(true){ l.add(new HeapOOMTest());//瘋狂建立對象 } } }
-Xms20m -Xmx20m做用是將jvm的最小堆容量和最大堆容量都設定爲20m,這樣就不會動態擴展jvm堆了
這段代碼瘋狂的建立對象,雖然對象沒有聲明變量名引用,可是將對象添加到隊列l中,這樣隊列l就持有了一份對象的引用
經過可達性算法(jvm判斷對象是否可被收集的算法)分析,隊列l做爲GC Root,每個對象都是l的一個可達的節點,因此瘋狂建立的對象不會被收集,這就是內存泄漏,這樣總有一天堆就溢出了。spa
運行結果:代理
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.LinkedList.linkLast(Unknown Source) at java.util.LinkedList.add(Unknown Source) at test.HeapOOMTest.main(HeapOOMTest.java:23)
程序發生內存溢出,並提示發生在Java heap spacecode
用visualVM工具分析堆快照
若是發生內存泄漏:
step1:找出泄漏的對象
step2:找到泄漏對象的GC Root
step3:根據泄漏對象和GC Root找到致使內存泄漏的代碼
step4:想法設法解除泄漏對象與GCRoot的鏈接
若是不存在泄漏:
優化程序,減少對象的生命週期
當發生堆溢出的時候,可讓程序在崩潰時產生一份堆內存快照
產生堆內存快照的方法:
給jvm加上參數XX:+HeapDumpOnOutofMemoryError,這樣就會在程序崩潰的時候,產生一份堆內存快照
分析堆內存快照我建議用jdk自帶的可視化監視工具visualVM,位置在jdk安裝目錄下的bin,若是是在linux環境的話,能夠把快照傳到window。由於分析工具會佔用很大的內存,不建議在服務端進行分析。
下面對剛纔程序產生的堆內存快照進行分析。
打開visualVM,裝入剛剛生成的快照,打開類標籤頁
隊列和瘋狂建立的對象幾乎佔滿了整個棧,想要讓垃圾收集器回收這些對象,要讓他們與GC Root斷開鏈接
雙擊HeapOOMTest類,跳轉到實例標籤頁,能夠查看這個類的全部實例
在實例上右鍵——顯示最近的垃圾回收根節點,能夠看到這個對象與根節點的鏈接
只要斷開HeapOOMTest對象與LinkedList的鏈接,這些瘋狂建立的對象就會被收集了
調用方法的時候,會在棧中入棧一個棧幀,若是當前棧的容量不足,就會發生棧溢出StackOverFlowError
那麼只要瘋狂的調用方法,而且有意的不讓棧幀出棧就能夠致使棧溢出了。
下面來一次棧溢出
//jvm參數:-Xss128k public class StackSOFTest { public void stackLeak(){ stackLeak();//遞歸,瘋狂的入棧,有意不讓出棧 } public static void main(String[] args){ StackSOFTest s=new StackSOFTest(); s.stackLeak(); } }
jvm設置參數-Xss128k,目的是縮小棧的空間,這樣棧溢出「來的快一點」
程序中用了遞歸,讓棧幀瘋狂的入棧,又不讓棧幀出棧,這樣就會棧溢出了。
運行結果:
Exception in thread "main" java.lang.StackOverflowError at test.StackSOFTest.stackLeak(StackSOFTest.java:17) at test.StackSOFTest.stackLeak(StackSOFTest.java:17)
這裏儲存的是一些常量、字面量。若是運行時常量池內存不足,就會發生內存溢出。從jdk1.7開始,運行時常量池移動到了堆中,因此若是堆的內存不足,也會致使運行時常量池內存溢出。
下面來一次運行時常量池溢出,環境是jdk8
只要建立足夠多的常量,就會發生溢出
/** * jvm參數: * jdk6之前:-XX:PermSize=10M -XX:MaxPermSize=10M * jdk7開始:-Xms10m -Xmx10m * */ public class RuntimePoolOOM { public static void main(String[] args){ int i=1; LinkedList<String> l=new LinkedList<String>();//保持常量的引用,防止被fullgc收集 while(true){ l.add(String.valueOf(i++).intern());//將常量添加到常量池 } } }
由於jdk6之前,運行時常量池是在方法區(永生代)中的,因此要限制永生代的容量,讓內存溢出來的更快。
從jdk7開始,運行時常量池是在堆中的,那麼固定堆的容量就行了
這裏用了鏈表去保存常量的引用,是由於防止被fullgc清理,由於fullgc會清理掉方法區和老年代
intern()方法是將常量添加到常量池中去,這樣運行時常量池一直都在增加,而後內存溢出
運行結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.lang.Integer.toString(Unknown Source) at java.lang.String.valueOf(Unknown Source) at test.RuntimePoolOOM.main(RuntimePoolOOM.java:30)
提示在heap區域發生內存溢出,果真運行時常量池被移到了堆中
方法區是存放類的信息,並且很難被gc,只要加載了大量類,就有可能引發方法區溢出
這裏將不作演示了,想試試的能夠用cglib建立大量的代理類
工做中也有可能會趕上方法區溢出:
當多個項目都有相同jar包的時候,又都存放在WEB-INF\lib\下,這樣每一個項目都會加載一遍jar包。會致使方法區中有大量相同類(被不一樣的類加載器所加載),又不會被gc掉。
若是實在不能瘦身類的話,那能夠擴大方法區的容量,給jvm指定參數-XX:MaxPermSize=xxxM
查看原文:http://blog.zswlib.com/2016/11/07/jvm%e5%86%85%e5%ad%98%e6%ba%a2%e5%87%ba%e5%88%86%e6%9e%90/