JAVA常见错误处理方法 和 JVM内存结构
OutOfMemoryError在開發(fā)過程中是司空見慣的,遇到這個錯誤,新手程序員都知道從兩個方面入手來解決:一是排查程序是否有BUG導(dǎo)致內(nèi)存泄漏;二是調(diào)整JVM啟動參數(shù)增大內(nèi)存。OutOfMemoryError有好幾種情況,每次遇到這個錯誤時,觀察OutOfMemoryError后面的提示信息,就可以發(fā)現(xiàn)不同之處,如:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
雖然都叫OutOfMemoryError,但每種錯誤背后的成因是不一樣的,解決方法也要視情況而定,不能一概而論。只有深入了解JVM的內(nèi)存結(jié)構(gòu)并仔細(xì)分析錯誤信息,才有可能做到對癥下藥,手到病除。
JVM規(guī)范
JVM規(guī)范對Java運行時的內(nèi)存劃定了幾塊區(qū)域(詳見這里),有:JVM棧(Java Virtual Machine Stacks)、堆(Heap)、方法區(qū)(Method Area)、常量池(Runtime Constant Pool)、本地方法棧(Native Method Stacks),但對各塊區(qū)域的內(nèi)存布局和地址空間卻沒有明確規(guī)定,而留給各JVM廠商發(fā)揮的空間。
HotSpot JVM
Sun自家的HotSpot JVM實現(xiàn)對堆內(nèi)存結(jié)構(gòu)有相對明確的說明。按照HotSpot JVM的實現(xiàn),堆內(nèi)存分為3個代:Young Generation、Old(Tenured) Generation、Permanent Generation。眾所周知,GC(垃圾收集)就是發(fā)生在堆內(nèi)存這三個代上面的。Young用于分配新的Java對象,其又被分為三個部分:Eden Space和兩塊Survivor Space(稱為From和To),Old用于存放在GC過程中從Young Gen中存活下來的對象,Permanent用于存放JVM加載的class等元數(shù)據(jù)。詳情參見HotSpot內(nèi)存管理白皮書。堆的布局圖示如下:
?
根據(jù)這些信息,我們可以推導(dǎo)出JVM規(guī)范的內(nèi)存分區(qū)和HotSpot實現(xiàn)中內(nèi)存區(qū)域的對應(yīng)關(guān)系:JVM規(guī)范的Heap對應(yīng)到Y(jié)oung和Old Generation,方法區(qū)和常量池對應(yīng)到Permanent Generation。對于Stack內(nèi)存,HotSpot實現(xiàn)也沒有詳細(xì)說明,但HotSpot白皮書上提到,Java線程棧是用宿主操作系統(tǒng)的棧和線程模型來表示的,Java方法和native方法共享相同的棧。因此,可以認(rèn)為在HotSpot中,JVM棧和本地方法棧是一回事。
操作系統(tǒng)
由于一個JVM進程首先是一個操作系統(tǒng)進程,因此會遵循操作系統(tǒng)進程地址空間的規(guī)定。32位系統(tǒng)的地址空間為4G,即最多表示4GB的虛擬內(nèi)存。在Linux系統(tǒng)中,高地址的1G空間(即0xC0000000~0xFFFFFFFF)被系統(tǒng)內(nèi)核占用,低地址的3G空間(即0×00000000~0xBFFFFFFF)為用戶程序所使用(顯然JVM進程運行在這3G的地址空間中)。這3G的地址空間從低到高又分為多個段;Text段用于存放程序二進制代碼;Data段用于存放編譯時已初始化的靜態(tài)變量;BSS段用于存放未初始化的靜態(tài)變量;Heap即堆,用于動態(tài)內(nèi)存分配的數(shù)據(jù)結(jié)構(gòu),C語言的malloc函數(shù)申請的內(nèi)存即是從此處分配的,Java的new實例化的對象也是自此分配。不同于前面三個段,Heap空間是可變的,其上界由低地址向高地址增長。內(nèi)存映射區(qū),加載的動態(tài)鏈接庫位于這個區(qū)中;Stack即棧空間,線程的執(zhí)行即是占用棧內(nèi)存,棧空間也是可變的,但它是通過下界從高地址向低地址移動而增長的。詳情參見這里。圖示如下:
?
JVM本身是由native code所編寫的,所以JVM進程同樣具有Text/Data/BSS/Heap/MemoryMapping/Stack等內(nèi)存段。而Java語言的Heap應(yīng)當(dāng)是建立在操作系統(tǒng)進程的Heap之上的,Java語言的Stack應(yīng)該也是建立操作系統(tǒng)進程Stack之上的。 綜合HotSpot的內(nèi)存區(qū)域和操作系統(tǒng)進程的地址空間,可以大致得到下列圖示:
Java線程的內(nèi)存是位于JVM或操作系統(tǒng)的棧(Stack)空間中,不同于對象——是位于堆(Heap)中。這是很多新手程序員容易誤解的地方。注意,“Java線程的內(nèi)存”這個用詞不是指Java.lang.Thread對象的內(nèi)存,java.lang.Thread對象本身是在Heap中分配的,當(dāng)調(diào)用start()方法之后,JVM會創(chuàng)建一個執(zhí)行單元,最終會創(chuàng)建一個操作系統(tǒng)的native thread來執(zhí)行,而這個執(zhí)行單元或native thread是使用Stack內(nèi)存空間的。
經(jīng)過上述鋪墊,可以得知,JVM進程的內(nèi)存大致分為Heap空間和Stack空間兩部分。Heap又分為Young、Old、Permanent三個代。Stack分為Java方法棧和native方法棧(不做區(qū)分),在Stack內(nèi)存區(qū)中,可以創(chuàng)建多個線程棧,每個線程棧占據(jù)Stack區(qū)中一小部分內(nèi)存,線程棧是一個LIFO數(shù)據(jù)結(jié)構(gòu),每調(diào)用一個方法,會在棧頂創(chuàng)建一個Frame,方法返回時,相應(yīng)的Frame會從棧頂移除(通過移動棧頂指針)。在這每一部分內(nèi)存中,都有可能會出現(xiàn)溢出錯誤。回到開頭的OutOfMemoryError,下面逐個說明錯誤原因和解決方法(每個OutOfMemoryError都有可能是程序BUG導(dǎo)致,因此解決方法不包括對BUG的排查)。
java.lang.OutOfMemoryError: Java heap space
原因:Heap內(nèi)存溢出,意味著Young和Old generation的內(nèi)存不夠。
解決:調(diào)整java啟動參數(shù) -Xms -Xmx 來增加Heap內(nèi)存。
java.lang.OutOfMemoryError: unable to create new native thread
原因:Stack空間不足以創(chuàng)建額外的線程,要么是創(chuàng)建的線程過多,要么是Stack空間確實小了。
解決:由于JVM沒有提供參數(shù)設(shè)置總的stack空間大小,但可以設(shè)置單個線程棧的大小;而系統(tǒng)的用戶空間一共是3G,除了Text/Data/BSS/MemoryMapping幾個段之外,Heap和Stack空間的總量有限,是此消彼長的。因此遇到這個錯誤,可以通過兩個途徑解決:1.通過-Xss啟動參數(shù)減少單個線程棧大小,這樣便能開更多線程(當(dāng)然不能太小,太小會出現(xiàn)StackOverflowError);2.通過-Xms -Xmx 兩參數(shù)減少Heap大小,將內(nèi)存讓給Stack(前提是保證Heap空間夠用)。
java.lang.OutOfMemoryError: PermGen space
原因:Permanent Generation空間不足,不能加載額外的類。
解決:調(diào)整-XX:PermSize= -XX:MaxPermSize= 兩個參數(shù)來增大PermGen內(nèi)存。一般情況下,這兩個參數(shù)不要手動設(shè)置,只要設(shè)置-Xmx足夠大即可,JVM會自行選擇合適的PermGen大小。
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
原因:這個錯誤比較少見(試著new一個長度1億的數(shù)組看看),同樣是由于Heap空間不足。如果需要new一個如此之大的數(shù)組,程序邏輯多半是不合理的。
解決:修改程序邏輯吧。或者也可以通過-Xmx來增大堆內(nèi)存。
在GC花費了大量時間,卻僅回收了少量內(nèi)存時,也會報出OutOfMemoryError,我只遇到過一兩次。當(dāng)使用-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC收集器時,在上述情況下會報錯,在HotSpot GC Turning文檔上有說明:
The parallel(concurrent) collector will throw an OutOfMemoryError if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be thrown.
對這個問題,一是需要進行GC turning,二是需要優(yōu)化程序邏輯。
java.lang.StackOverflowError
原因:這也內(nèi)存溢出錯誤的一種,即線程棧的溢出,要么是方法調(diào)用層次過多(比如存在無限遞歸調(diào)用),要么是線程棧太小。
解決:優(yōu)化程序設(shè)計,減少方法調(diào)用層次;調(diào)整-Xss參數(shù)增加線程棧大小。
?
本文轉(zhuǎn)自
http://blog.csdn.net/xiaoyufu007/article/details/6429657
轉(zhuǎn)載于:https://www.cnblogs.com/ywl925/p/4010825.html
總結(jié)
以上是生活随笔為你收集整理的JAVA常见错误处理方法 和 JVM内存结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UVa 1626 (输出方案) Brac
- 下一篇: win7上帝模式