Java常見內(nèi)存溢出異常分析與解決
Java虛擬機規(guī)范規(guī)定JVM的內(nèi)存分為了好幾塊,比如堆,棧,程序計數(shù)器,方法區(qū)等,而Hotspot jvm的實現(xiàn)中,將堆內(nèi)存分為了三部分,新生代,老年代,持久帶,其中持久帶實現(xiàn)了規(guī)范中規(guī)定的方法區(qū),而內(nèi)存模型中不同的部分都會出現(xiàn)相應的OutOfMemoryError錯誤,接下來我們就分開來討論一下。java.lang.OutOfMemoryError這個錯誤我相信大部分開發(fā)人員都有遇到過,產(chǎn)生該錯誤的原因大都出于以下原因:
JVM內(nèi)存過小、程序不嚴密,產(chǎn)生了過多的垃圾。
導致OutOfMemoryError異常的常見原因有以下幾種:
- 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);
- 集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;
- 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復的對象實體;
- 使用的第三方軟件中的BUG;
- 啟動參數(shù)內(nèi)存值設定的過??;
此錯誤常見的錯誤提示:
- tomcat:java.lang.OutOfMemoryError: PermGen space
- tomcat:java.lang.OutOfMemoryError: Java heap space
- weblogic:Root cause of ServletException java.lang.OutOfMemoryError
- resin:java.lang.OutOfMemoryError
- java:java.lang.OutOfMemoryError
棧溢出(StackOverflowError)
棧溢出拋出java.lang.StackOverflowError錯誤,出現(xiàn)此種情況是因為方法運行的時候棧的深度超過了虛擬機容許的最大深度所致。出現(xiàn)這種情況,一般情況下是程序錯誤所致的,比如寫了一個死遞歸,就有可能造成此種情況。 下面我們通過一段代碼來模擬一下此種情況的內(nèi)存溢出。
import java.util.*; import java.lang.*; public class OOMTest{ public void stackOverFlowMethod(){ stackOverFlowMethod(); } public static void main(String... args){ OOMTest oom = new OOMTest(); oom.stackOverFlowMethod(); } }
運行上面的代碼,會拋出如下的異常:
Exception in thread "main" java.lang.StackOverflowError at OOMTest.stackOverFlowMethod(OOMTest.java:6)
堆溢出(OutOfMemoryError:java heap space)
堆內(nèi)存溢出的時候,虛擬機會拋出java.lang.OutOfMemoryError:Java heap space,出現(xiàn)此種情況的時候,我們需要根據(jù)內(nèi)存溢出的時候產(chǎn)生的dump文件來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動參數(shù))。出現(xiàn)此種問題的時候有可能是內(nèi)存泄露,也有可能是內(nèi)存溢出了。
如果內(nèi)存泄露,我們要找出泄露的對象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。
如果出現(xiàn)了內(nèi)存溢出問題,這往往是程序本生需要的內(nèi)存大于了我們給虛擬機配置的內(nèi)存,這種情況下,我們可以采用調(diào)大-Xmx來解決這種問題。
下面我們通過如下的代碼來演示一下此種情況的溢出:
import java.util.*; import java.lang.*; public class OOMTest{ public static void main(String... args){ List<byte[]> buffer = new ArrayList<byte[]>(); buffer.add(new byte[10*1024*1024]); } }
我們通過如下的命令運行上面的代碼:
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)
從運行結果可以看出,JVM進行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以后old區(qū)使用率為134K,而字節(jié)數(shù)組為10M,加起來大于了old generation的空間,所以拋出了異常,如果調(diào)整-Xms21M,-Xmx21M,那么就不會觸發(fā)gc操作也不會出現(xiàn)異常了。
通過上面的實驗其實也從側(cè)面驗證了一個結論:當對象大于新生代剩余內(nèi)存的時候,將直接放入老年代,當老年代剩余內(nèi)存還是無法放下的時候,觸發(fā)垃圾收集,收集后還是不能放下就會拋出內(nèi)存溢出異常了
持久帶溢出(OutOfMemoryError: PermGen space)
我們知道Hotspot jvm通過持久帶實現(xiàn)了Java虛擬機規(guī)范中的方法區(qū),而運行時的常量池就是保存在方法區(qū)中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區(qū)中保存的class對象沒有被及時回收掉或者class信息占用的內(nèi)存超過了我們配置。當持久帶溢出的時候拋出java.lang.OutOfMemoryError: PermGen space。
我在工作可能在如下幾種場景下出現(xiàn)此問題。
使用一些應用服務器的熱部署的時候,我們就會遇到熱部署幾次以后發(fā)現(xiàn)內(nèi)存溢出了,這種情況就是因為每次熱部署的后,原來的class沒有被卸載掉。
如果應用程序本身比較大,涉及的類庫比較多,但是我們分配給持久帶的內(nèi)存(通過-XX:PermSize和-XX:MaxPermSize來設置)比較小的時候也可能出現(xiàn)此種問題。
一些第三方框架,比如spring,hibernate都通過字節(jié)碼生成技術(比如CGLib)來實現(xiàn)一些增強的功能,這種情況可能需要更大的方法區(qū)來存儲動態(tài)生成的Class文件。
我們知道Java中字符串常量是放在常量池中的,String.intern()這個方法運行的時候,會檢查常量池中是否存和本字符串相等的對象,如果存在直接返回對常量池中對象的引用,不存在的話,先把此字符串加入常量池,然后再返回字符串的引用。那么我們就可以通過String.intern方法來模擬一下運行時常量區(qū)的溢出.下面我們通過如下的代碼來模擬此種情況:
import java.util.*; import java.lang.*; public class OOMTest{ public static void main(String... args){ List<String> list = new ArrayList<String>(); while(true){ list.add(UUID.randomUUID().toString().intern()); } } }
我們通過如下的命令運行上面代碼:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest
運行后的輸入如下圖所示:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at OOMTest.main(OOMTest.java:8)
通過上面的代碼,我們成功模擬了運行時常量池溢出的情況,從輸出中的PermGen space可以看出確實是持久帶發(fā)生了溢出,這也驗證了,我們前面說的Hotspot jvm通過持久帶來實現(xiàn)方法區(qū)的說法。
OutOfMemoryError:unable to create native thread
最后我們在來看看java.lang.OutOfMemoryError:unable to create natvie thread這種錯誤。 出現(xiàn)這種情況的時候,一般是下面兩種情況導致的:
程序創(chuàng)建的線程數(shù)超過了操作系統(tǒng)的限制。對于Linux系統(tǒng),我們可以通過ulimit -u來查看此限制。
給虛擬機分配的內(nèi)存過大,導致創(chuàng)建線程的時候需要的native內(nèi)存太少。我們都知道操作系統(tǒng)對每個進程的內(nèi)存是有限制的,我們啟動Jvm,相當于啟動了一個進程,假如我們一個進程占用了4G的內(nèi)存,那么通過下面的公式計算出來的剩余內(nèi)存就是建立線程棧的時候可以用的內(nèi)存。 線程??偪捎脙?nèi)存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計數(shù)器占用的內(nèi)存 通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那么留給線程??捎玫目臻g就越小,在-Xss參數(shù)配置的棧容量不變的情況下,可以創(chuàng)建的線程數(shù)也就越小。因此如果是因為這種情況導致的unable to create native thread,那么要么我們增大進程所占用的總內(nèi)存,或者減少-Xmx或者-Xss來達到創(chuàng)建更多線程的目的。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
分享關于JAVA 中使用Preferences讀寫注冊表時要注意的地方
這篇文章介紹了關于JAVA 中使用Preferences讀寫注冊表時要注意的地方,有需要的朋友可以參考一下2013-08-08Spring Boot JPA如何把ORM統(tǒng)一起來
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎上封裝的一套JPA應用框架,可使開發(fā)者用極簡的代碼即可實現(xiàn)對數(shù)據(jù)的訪問和操作,本文給大家詳細介紹了Spring Boot JPA如何把ORM統(tǒng)一起來,感興趣的朋友一起看看吧2018-04-04springboot讀取bootstrap配置及knife4j版本兼容性問題及解決
這篇文章主要介紹了springboot讀取bootstrap配置及knife4j版本兼容性問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06Java實現(xiàn)基于UDP協(xié)議的網(wǎng)絡通信UDP編程
在Java中使用UDP編程,仍然需要使用Socket,因為應用程序在使用UDP時必須指定網(wǎng)絡接口(IP地址)和端口號。注意:UDP端口和TCP端口雖然都使用0~65535,但他們是兩套獨立的端口,即一個應用程序用TCP占用了端口1234,不影響另一個應用程序用UDP占用端口12342023-04-04使用javaweb項目對數(shù)據(jù)庫增、刪、改、查操作的實現(xiàn)方法
這篇文章主要給大家介紹了關于使用javaweb項目對數(shù)據(jù)庫增、刪、改、查操作的實現(xiàn)方法,avaWeb是指使用Java語言進行Web應用程序開發(fā)的技術,可以利用Java編寫一些動態(tài)網(wǎng)頁、交互式網(wǎng)頁、企業(yè)級應用程序等,需要的朋友可以參考下2023-07-07RedisTemplate.opsForHash()用法簡介并舉例說明
redistemplate.opsforhash是RedisTemplate模板類中的一個方法,用于獲取操作哈希數(shù)據(jù)類型的接口,這篇文章主要給大家介紹了關于RedisTemplate.opsForHash()用法簡介并舉例說明的相關資料,需要的朋友可以參考下2024-06-06詳解Java Bellman-Ford算法原理及實現(xiàn)
Bellman-Ford算法與Dijkstra算法類似,都是以松弛操作作為基礎,Bellman-Ford算法是對所有邊都進行松弛操作,本文將詳解Bellman-Ford算法原理及實現(xiàn),感興趣的可以了解一下2022-07-07