云自無心水自閑

          天平山上白云泉,云自無心水自閑。何必奔沖山下去,更添波浪向人間!
          posts - 288, comments - 524, trackbacks - 0, articles - 6
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
          Java虛擬機(jī)規(guī)范規(guī)定JVM的內(nèi)存分為了好幾塊,比如堆,棧,程序計(jì)數(shù)器,方法區(qū)等,而Hotspot jvm的實(shí)現(xiàn)中,將堆內(nèi)存分為了三部分,新生代,老年代,持久帶,其中持久帶實(shí)現(xiàn)了規(guī)范中規(guī)定的方法區(qū),而內(nèi)存模型中不同的部分都會(huì)出現(xiàn)相應(yīng)的OOM錯(cuò)誤,接下來我們就分開來討論一下。 

          棧溢出(StackOverflowError) 

          棧溢出拋出java.lang.StackOverflowError錯(cuò)誤,出現(xiàn)此種情況是因?yàn)榉椒ㄟ\(yùn)行的時(shí)候棧的深度超過了虛擬機(jī)容許的最大深度所致。 

          出現(xiàn)這種情況,一般情況下是程序錯(cuò)誤所致的,比如寫了一個(gè)死遞歸,就有可能造成此種情況。 下面我們通過一段代碼來模擬一下此種情況的內(nèi)存溢出。 
          Java代碼 
          1. import java.util.*;  
          2. import java.lang.*;  
          3. public class OOMTest{  
          4.    
          5.   public void stackOverFlowMethod(){  
          6.       stackOverFlowMethod();  
          7.   }  
          8.    
          9.   public static void main(String... args){  
          10.       OOMTest oom = new OOMTest();  
          11.       oom.stackOverFlowMethod();  
          12.   }  
          13.    
          14. }  

          運(yùn)行上面的代碼,會(huì)拋出如下的異常: 
          引用

          Exception in thread "main" java.lang.StackOverflowError 
                  at OOMTest.stackOverFlowMethod(OOMTest.java:6) 

          堆溢出(OutOfMemoryError:java heap space) 

          堆內(nèi)存溢出的時(shí)候,虛擬機(jī)會(huì)拋出java.lang.OutOfMemoryError:java heap space,出現(xiàn)此種情況的時(shí)候,我們需要根據(jù)內(nèi)存溢出的時(shí)候產(chǎn)生的dump文件來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動(dòng)參數(shù))。出現(xiàn)此種問題的時(shí)候有可能是內(nèi)存泄露,也有可能是內(nèi)存溢出了。 
          如果內(nèi)存泄露,我們要找出泄露的對(duì)象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。 
          如果出現(xiàn)了內(nèi)存溢出問題,這往往是程序本生需要的內(nèi)存大于了我們給虛擬機(jī)配置的內(nèi)存,這種情況下,我們可以采用調(diào)大-Xmx來解決這種問題。 

          下面我們通過如下的代碼來演示一下此種情況的溢出: 
          Java代碼 
          1. import java.util.*;  
          2. import java.lang.*;  
          3. public class OOMTest{  
          4.    
          5.         public static void main(String... args){  
          6.                 List<byte[]> buffer = new ArrayList<byte[]>();  
          7.                 buffer.add(new byte[10*1024*1024]);  
          8.         }  
          9.    
          10. }  

          我們通過如下的命令運(yùn)行上面的代碼: 

          Java代碼 
          1. java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest  


          程序輸入如下的信息: 
          引用

          [GC 1180K->366K(19456K), 0.0037311 secs] 
          [Full GC 366K->330K(19456K), 0.0098740 secs] 
          [Full GC 330K->292K(19456K), 0.0090244 secs] 
          Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
                  at OOMTest.main(OOMTest.java:7) 

          從運(yùn)行結(jié)果可以看出,JVM進(jìn)行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以后old區(qū)使用率為134K,而字節(jié)數(shù)組為10M,加起來大于了old generation的空間,所以拋出了異常,如果調(diào)整-Xms21M,-Xmx21M,那么就不會(huì)觸發(fā)gc操作也不會(huì)出現(xiàn)異常了。 

          通過上面的實(shí)驗(yàn)其實(shí)也從側(cè)面驗(yàn)證了一個(gè)結(jié)論:當(dāng)對(duì)象大于新生代剩余內(nèi)存的時(shí)候,將直接放入老年代,當(dāng)老年代剩余內(nèi)存還是無法放下的時(shí)候,出發(fā)垃圾收集,收集后還是不能放下就會(huì)拋出內(nèi)存溢出異常了 

          持久帶溢出(OutOfMemoryError: PermGen space) 

          我們知道Hotspot jvm通過持久帶實(shí)現(xiàn)了Java虛擬機(jī)規(guī)范中的方法區(qū),而運(yùn)行時(shí)的常量池就是保存在方法區(qū)中的,因此持久帶溢出有可能是運(yùn)行時(shí)常量池溢出,也有可能是方法區(qū)中保存的class對(duì)象沒有被及時(shí)回收掉或者class信息占用的內(nèi)存超過了我們配置。當(dāng)持久帶溢出的時(shí)候拋出java.lang.OutOfMemoryError: PermGen space。 
          我在工作可能在如下幾種場(chǎng)景下出現(xiàn)此問題。 

          1.使用一些應(yīng)用服務(wù)器的熱部署的時(shí)候,我們就會(huì)遇到熱部署幾次以后發(fā)現(xiàn)內(nèi)存溢出了,這種情況就是因?yàn)槊看螣岵渴鸬暮螅瓉淼腸lass沒有被卸載掉。 
          2.如果應(yīng)用程序本身比較大,涉及的類庫(kù)比較多,但是我們分配給持久帶的內(nèi)存(通過-XX:PermSize和-XX:MaxPermSize來設(shè)置)比較小的時(shí)候也可能出現(xiàn)此種問題。 
          3.一些第三方框架,比如spring,hibernate都通過字節(jié)碼生成技術(shù)(比如CGLib)來實(shí)現(xiàn)一些增強(qiáng)的功能,這種情況可能需要更大的方法區(qū)來存儲(chǔ)動(dòng)態(tài)生成的Class文件。 
          我們知道Java中字符串常量是放在常量池中的,String.intern()這個(gè)方法運(yùn)行的時(shí)候,會(huì)檢查常量池中是否存和本字符串相等的對(duì)象,如果存在直接返回對(duì)常量池中對(duì)象的引用,不存在的話,先把此字符串加入常量池,然后再返回字符串的引用。那么我們就可以通過String.intern方法來模擬一下運(yùn)行時(shí)常量區(qū)的溢出.下面我們通過如下的代碼來模擬此種情況: 
          Java代碼 
          1. import java.util.*;  
          2. import java.lang.*;  
          3. public class OOMTest{  
          4.    
          5.         public static void main(String... args){  
          6.                 List<String> list = new ArrayList<String>();  
          7.                 while(true){  
          8.                         list.add(UUID.randomUUID().toString().intern());  
          9.                 }  
          10.         }  
          11.    
          12. }  

          我們通過如下的命令運(yùn)行上面代碼: 
          java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest 
          運(yùn)行后的輸入如下圖所示: 
          引用

          Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
                  at java.lang.String.intern(Native Method) 
                  at OOMTest.main(OOMTest.java:8) 

          通過上面的代碼,我們成功模擬了運(yùn)行時(shí)常量池溢出的情況,從輸出中的PermGen space可以看出確實(shí)是持久帶發(fā)生了溢出,這也驗(yàn)證了,我們前面說的Hotspot jvm通過持久帶來實(shí)現(xiàn)方法區(qū)的說法。 

          OutOfMemoryError:unable to create native thread 

          最后我們?cè)趤砜纯磈ava.lang.OutOfMemoryError:unable to create natvie thread這種錯(cuò)誤。 出現(xiàn)這種情況的時(shí)候,一般是下面兩種情況導(dǎo)致的: 

          1.程序創(chuàng)建的線程數(shù)超過了操作系統(tǒng)的限制。對(duì)于Linux系統(tǒng),我們可以通過ulimit -u來查看此限制。 
          給虛擬機(jī)分配的內(nèi)存過大,導(dǎo)致創(chuàng)建線程的時(shí)候需要的native內(nèi)存太少。我們都知道操作系統(tǒng)對(duì)每個(gè)進(jìn)程的內(nèi)存是有限制的,我們啟動(dòng)Jvm,相當(dāng)于啟動(dòng)了一個(gè)進(jìn)程,假如我們一個(gè)進(jìn)程占用了4G的內(nèi)存,那么通過下面的公式計(jì)算出來的剩余內(nèi)存就是建立線程棧的時(shí)候可以用的內(nèi)存。 線程??偪捎脙?nèi)存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計(jì)數(shù)器占用的內(nèi)存 通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那么留給線程棧可用的空間就越小,在-Xss參數(shù)配置的棧容量不變的情況下,可以創(chuàng)建的線程數(shù)也就越小。因此如果是因?yàn)檫@種情況導(dǎo)致的unable to create native thread,那么要么我們?cè)龃筮M(jìn)程所占用的總內(nèi)存,或者減少-Xmx或者-Xss來達(dá)到創(chuàng)建更多線程的目的。


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 托克逊县| 通河县| 合阳县| 滨海县| 抚远县| 宜兰市| 绥江县| 开封市| 海淀区| 南靖县| 钦州市| 那坡县| 大理市| 增城市| 资阳市| 普陀区| 额济纳旗| 黑龙江省| 北票市| 北辰区| 丽江市| 南靖县| 民丰县| 鹤山市| 衡水市| 宁南县| 庆安县| 贵港市| 枣强县| 马山县| 连山| 东阳市| 疏勒县| 乐清市| 新密市| 钦州市| 博罗县| 上高县| 东阿县| 富顺县| 江永县|