轉(zhuǎn)載:http://www.itdecent.cn/p/cd705f88cf2a
1、內(nèi)存溢出和內(nèi)存泄漏的區(qū)別
內(nèi)存溢出 (Out Of Memory):是指程序在申請內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用,出現(xiàn)Out Of Memory。
內(nèi)存泄露 (Memory Leak):是指程序在申請內(nèi)存后,由于某種原因無法釋放已申請的內(nèi)存空間,導(dǎo)致這塊內(nèi)存無法再次被利用,造成系統(tǒng)內(nèi)存的浪費(fèi)。
一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積的后果很嚴(yán)重,無論多少內(nèi)存,遲早會(huì)被占光。
Memory Leak 會(huì)最終會(huì)導(dǎo)致 Out Of Memory 。
2、內(nèi)存溢出分類
2.1 棧內(nèi)存溢出(StackOverflowError):
程序所要求的棧深度過大導(dǎo)致,可以寫一個(gè)死遞歸程序觸發(fā)。
2.2 堆內(nèi)存溢出(OutOfMemoryError : java heap space)
需要分清是 內(nèi)存溢出 還是 內(nèi)存泄漏:
(1)如果是內(nèi)存溢出,則通過 調(diào)大 -Xms,-Xmx參數(shù)。
(2)如果是內(nèi)存泄露,則看對象如何被 GC Root 引用。
2.3 持久帶內(nèi)存溢出(OutOfMemoryError: PermGen space)
持久帶中包含方法區(qū),方法區(qū)包含常量池。
因此持久帶溢出有可能是(1) 運(yùn)行時(shí)常量池溢出,也有可能是(2)方法區(qū)中保存的Class對象沒有被及時(shí)回收掉或者Class信息占用的內(nèi)存超過了我們配置。
用String.intern()觸發(fā)常量池溢出。
Class對象未被釋放,Class對象占用信息過多,有過多的Class對象。可以導(dǎo)致持久帶內(nèi)存溢出。
2.4 無法創(chuàng)建本地線程
Caused by: java.lang.OutOfMemoryError:unable to create new native thread
系統(tǒng)內(nèi)存的總?cè)萘坎蛔儯褍?nèi)存、非堆內(nèi)存設(shè)置過大,會(huì)導(dǎo)致能給線程分配的內(nèi)存不足。
3、內(nèi)存溢出詳解
3.1 棧溢出(StackOverflowError)
棧溢出拋出 StackOverflowError 錯(cuò)誤,出現(xiàn)此種情況是因?yàn)榉椒ㄟ\(yùn)行的時(shí)候棧的深度超過了虛擬機(jī)容許的最大深度所致。一般情況下是程序錯(cuò)誤所致的,比如寫了一個(gè)死遞歸,就有可能造成此種情況。下面我們通過一段代碼來模擬一下此種情況的內(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();
}
}
運(yùn)行上面的代碼,會(huì)拋出如下的異常:
Exception in thread "main" java.lang.StackOverflowError
at OOMTest.stackOverFlowMethod(OOMTest.java:6)
對于棧內(nèi)存溢出,根據(jù)《Java 虛擬機(jī)規(guī)范》中文版:
如果線程請求的棧容量超過棧允許的最大容量的話,Java 虛擬機(jī)將拋出一個(gè)StackOverflow異常;如果Java虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過,但是無法申請到足夠的內(nèi)存去完成擴(kuò)展,或者在新建立線程的時(shí)候沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的虛擬機(jī)棧,那么Java虛擬機(jī)將拋出一個(gè)OutOfMemory 異常。
3.2 堆溢出(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:+HeapDumpOnOutOfMemoryError jvm啟動(dòng)參數(shù))。出現(xiàn)此種問題的時(shí)候有可能是內(nèi)存泄漏,也有可能是內(nèi)存溢出了。
1、配置方法
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目錄}。
2、參數(shù)說明
(1)-XX:+HeapDumpOnOutOfMemoryError參數(shù)表示當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件。
(2)-XX:HeapDumpPath=${目錄}參數(shù)表示生成DUMP文件的路徑,也可以指定文件名稱,例如:-XX:HeapDumpPath=${目錄}/java_heapdump.hprof。如果不指定文件名,默認(rèn)為:java_<pid><date><time>_heapDump.hprof。
如果是內(nèi)存泄漏,我們要找出內(nèi)存泄漏的對象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。
如果出現(xiàn)了內(nèi)存溢出問題,這往往是程序本生需要的內(nèi)存大于了我們給虛擬機(jī)配置的內(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]);
}
}
我們通過如下的命令運(yùn)行上面的代碼:
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é)論:對象大于新生代剩余內(nèi)存的時(shí)候,將直接放入老年代,當(dāng)老年代剩余內(nèi)存還是無法放下的時(shí)候,觸發(fā)垃圾收集,收集后還是不能放下就會(huì)拋出內(nèi)存溢出異常了。
3.3 持久帶溢出(OutOfMemoryError: PermGen space)
我們知道 Hotspot jvm 通過持久帶實(shí)現(xiàn)了Java虛擬機(jī)規(guī)范中的方法區(qū),而運(yùn)行時(shí)的常量池就是保存在方法區(qū)中。
因此持久帶溢出有可能是:
(1) 運(yùn)行時(shí)常量池溢出。
(2)方法區(qū)中保存Class對象沒有被及時(shí)回收掉或者Class信息占用的內(nèi)存超過了我們配置。
當(dāng)持久帶溢出的時(shí)候拋出 java.lang.OutOfMemoryError: PermGen space??赡茉谌缦聨追N場景下出現(xiàn):
使用一些應(yīng)用服務(wù)器的熱部署的時(shí)候,我們就會(huì)遇到熱部署幾次以后發(fā)現(xiàn)內(nèi)存溢出了,這種情況就是因?yàn)槊看螣岵渴鸬暮?,原來的Class沒有被卸載掉。
如果應(yīng)用程序本身比較大,涉及的類庫比較多,但是我們分配給持久帶的內(nèi)存(通過-XX:PermSize和-XX:MaxPermSize來設(shè)置)比較小的時(shí)候也可能出現(xiàn)此種問題。
一些第三方框架,比如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ì)檢查常量池中是否存和本字符串相等的對象,如果存在直接返回常量池中對象的引用,不存在的話,先把此字符串加入常量池,然后再返回字符串的引用。
那么我們就可以通過String.intern方法來模擬一下運(yùn)行時(shí)常量區(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());
}
}
}
我們通過如下的命令運(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ū)的說法。
3.4 無法創(chuàng)建本地線程
java.lang.OutOfMemoryError: unable to create new native thread
最后我們在來看看java.lang.OutOfMemoryError:unable to create new native thread這種錯(cuò)誤。 出現(xiàn)這種情況的時(shí)候,一般是下面兩種情況導(dǎo)致的:
程序創(chuàng)建的線程數(shù)超過了操作系統(tǒng)的限制。對于Linux系統(tǒng),我們可以通過 ulimit -u 來查看此限制。
給虛擬機(jī)分配的內(nèi)存過大,導(dǎo)致創(chuàng)建線程的時(shí)候需要的native內(nèi)存太少。
建立每個(gè)線程,都需要給這個(gè)線程分配??臻g。
我們都知道操作系統(tǒng)對每個(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的值越大,那么留給線程??捎玫目臻g就越小,在-Xss參數(shù)配置的棧容量不變的情況下,可以創(chuàng)建的線程數(shù)也就越小。因此如果是因?yàn)檫@種情況導(dǎo)致的unable to create native thread,那么要么我們增大進(jìn)程所占用的總內(nèi)存,或者減少-Xmx或者-Xss來達(dá)到創(chuàng)建更多線程的目的。