最近查找了很多關(guān)于OOM,甚至于Java內(nèi)存管理以及JVM的相關(guān)資料,發(fā)現(xiàn)這方面的東西太多了,竟有一種眼花繚亂的感覺,要想了解全面的話,恐非一篇文章能說清的,因此按照自己的理解整理了一篇,剩下的還需要繼續(xù)學(xué)習(xí)。
1)什么是OOM? OOM,全稱“Out Of Memory”,翻譯成中文就是“內(nèi)存用完了”,來源于java.lang.OutOfMemoryError??聪玛P(guān)于的官方說明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是說,當(dāng)JVM因?yàn)闆]有足夠的內(nèi)存來為對象分配空間并且垃圾回收器也已經(jīng)沒有空間可回收時(shí),就會拋出這個(gè)error(注:非exception,因?yàn)檫@個(gè)問題已經(jīng)嚴(yán)重到不足以被應(yīng)用處理)。
2)為什么會OOM?
為什么會沒有內(nèi)存了呢?原因不外乎有兩點(diǎn):
1)分配的少了:比如虛擬機(jī)本身可使用的內(nèi)存(一般通過啟動時(shí)的VM參數(shù)指定)太少。
2)應(yīng)用用的太多,并且用完沒釋放,浪費(fèi)了。此時(shí)就會造成內(nèi)存泄露或者內(nèi)存溢出。
內(nèi)存泄露:申請使用完的內(nèi)存沒有釋放,導(dǎo)致虛擬機(jī)不能再次使用該內(nèi)存,此時(shí)這段內(nèi)存就泄露了,因?yàn)樯暾堈卟挥昧耍植荒鼙惶摂M機(jī)分配給別人用。
內(nèi)存溢出:申請的內(nèi)存超出了JVM能提供的內(nèi)存大小,此時(shí)稱之為溢出。
在之前沒有垃圾自動回收的日子里,比如C語言和C++語言,我們必須親自負(fù)責(zé)內(nèi)存的申請與釋放操作,如果申請了內(nèi)存,用完后又忘記了釋放,比如C++中的new了但是沒有delete,那么就可能造成內(nèi)存泄露。偶爾的內(nèi)存泄露可能不會造成問題,而大量的內(nèi)存泄露可能會導(dǎo)致內(nèi)存溢出。
而在Java語言中,由于存在了垃圾自動回收機(jī)制,所以,我們一般不用去主動釋放不用的對象所占的內(nèi)存,也就是理論上來說,是不會存在“內(nèi)存泄露”的。但是,如果編碼不當(dāng),比如,將某個(gè)對象的引用放到了全局的Map中,雖然方法結(jié)束了,但是由于垃圾回收器會根據(jù)對象的引用情況來回收內(nèi)存,導(dǎo)致該對象不能被及時(shí)的回收。如果該種情況出現(xiàn)次數(shù)多了,就會導(dǎo)致內(nèi)存溢出,比如系統(tǒng)中經(jīng)常使用的緩存機(jī)制。Java中的內(nèi)存泄露,不同于C++中的忘了delete,往往是邏輯上的原因泄露。
3)OOM的類型
JVM內(nèi)存模型:
按照J(rèn)VM規(guī)范,JAVA虛擬機(jī)在運(yùn)行時(shí)會管理以下的內(nèi)存區(qū)域:
- 程序計(jì)數(shù)器:當(dāng)前線程執(zhí)行的字節(jié)碼的行號指示器,線程私有
- JAVA虛擬機(jī)棧:Java方法執(zhí)行的內(nèi)存模型,每個(gè)Java方法的執(zhí)行對應(yīng)著一個(gè)棧幀的進(jìn)棧和出棧的操作。
- 本地方法棧:類似“ JAVA虛擬機(jī)棧 ”,但是為native方法的運(yùn)行提供內(nèi)存環(huán)境。
- JAVA堆:對象內(nèi)存分配的地方,內(nèi)存垃圾回收的主要區(qū)域,所有線程共享。可分為新生代,老生代。
- 方法區(qū):用于存儲已經(jīng)被JVM加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。Hotspot中的“永久代”。
- 運(yùn)行時(shí)常量池:方法區(qū)的一部分,存儲常量信息,如各種字面量、符號引用等。
- 直接內(nèi)存:并不是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分, 可直接訪問的內(nèi)存, 比如NIO會用到這部分。
按照J(rèn)VM規(guī)范,除了程序計(jì)數(shù)器不會拋出OOM外,其他各個(gè)內(nèi)存區(qū)域都可能會拋出OOM。
最常見的OOM情況有以下三種:
- java.lang.OutOfMemoryError: Java heap space ------>java堆內(nèi)存溢出,此種情況最常見,一般由于內(nèi)存泄露或者堆的大小設(shè)置不當(dāng)引起。對于內(nèi)存泄露,需要通過內(nèi)存監(jiān)控軟件查找程序中的泄露代碼,而堆大小可以通過虛擬機(jī)參數(shù)-Xms,-Xmx等修改。
- java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法區(qū)溢出了,一般出現(xiàn)于大量Class或者jsp頁面,或者采用cglib等反射機(jī)制的情況,因?yàn)樯鲜銮闆r會產(chǎn)生大量的Class信息存儲于方法區(qū)。此種情況可以通過更改方法區(qū)的大小來解決,使用類似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,過多的常量尤其是字符串也會導(dǎo)致方法區(qū)溢出。
- java.lang.StackOverflowError ------> 不會拋OOM error,但也是比較常見的Java內(nèi)存溢出。JAVA虛擬機(jī)棧溢出,一般是由于程序中存在死循環(huán)或者深度遞歸調(diào)用造成的,棧大小設(shè)置太小也會出現(xiàn)此種溢出??梢酝ㄟ^虛擬機(jī)參數(shù)-Xss來設(shè)置棧的大小。
4)OOM分析--heapdump
要dump堆的內(nèi)存鏡像,可以采用如下兩種方式:
- 設(shè)置JVM參數(shù)-XX:+HeapDumpOnOutOfMemoryError,設(shè)定當(dāng)發(fā)生OOM時(shí)自動dump出堆信息。不過該方法需要JDK5以上版本。
- 使用JDK自帶的jmap命令。"jmap -dump:format=b,file=heap.bin <pid>" 其中pid可以通過jps獲取。
dump堆內(nèi)存信息后,需要對dump出的文件進(jìn)行分析,從而找到OOM的原因。常用的工具有:
- mat: eclipse memory analyzer, 基于eclipse RCP的內(nèi)存分析工具。詳細(xì)信息參見:http://www.eclipse.org/mat/,推薦使用。
- jhat:JDK自帶的java heap analyze tool,可以將堆中的對象以html的形式顯示出來,包括對象的數(shù)量,大小等等,并支持對象查詢語言O(shè)QL,分析相關(guān)的應(yīng)用后,可以通過http://localhost:7000來訪問分析結(jié)果。不推薦使用,因?yàn)樵趯?shí)際的排查過程中,一般是先在生產(chǎn)環(huán)境 dump出文件來,然后拉到自己的開發(fā)機(jī)器上分析,所以,不如采用高級的分析工具比如前面的mat來的高效。
這個(gè)鏈接:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html中提供了一個(gè)采用mat分析的例子 。
注意:因?yàn)镴VM規(guī)范沒有對dump出的文件的格式進(jìn)行定義,所以不同的虛擬機(jī)產(chǎn)生的dump文件并不是一樣的。在分析時(shí),需要針對不同的虛擬機(jī)的輸出采用不同的分析工具(當(dāng)然,有的工具可以兼容多個(gè)虛擬機(jī)的格式)。IBM HeapAnalyzer也是分析heap的一個(gè)常用的工具。
5)小結(jié)
涉及到的虛擬機(jī)的技術(shù)或者工具,往往需要考慮到虛擬機(jī)規(guī)范以及不同的虛擬機(jī)實(shí)現(xiàn)。尤其是針對虛擬機(jī)調(diào)優(yōu)時(shí),往往需要針對虛擬機(jī)在某些方面的實(shí)現(xiàn)策略來考慮,比如,不同的虛擬機(jī)的垃圾回收算法是不一樣的,而這直接影響了虛擬機(jī)某些參數(shù)的設(shè)置,以達(dá)到虛擬機(jī)的最佳性能。
而針對JVM運(yùn)行時(shí)的分析與診斷,則需要掌握分析基本方法,針對具體情況,運(yùn)用虛擬機(jī)的原理,具體分析。一句話,水很深啊。