OOM這個(gè)縮寫就是Java程序開發(fā)過(guò)程中讓人最頭痛的問(wèn)題:Out of Memory。在很多開發(fā)人員的開發(fā)過(guò)程中,或多或少的都會(huì)遇到這類問(wèn)題,這類問(wèn)題定位比較困難,往往需要根據(jù)經(jīng)驗(yàn)來(lái)判斷可能出現(xiàn)問(wèn)題的代碼。原因主要是 兩個(gè):對(duì)象沒有被釋放(多種情況引起,往往是比較隱蔽的引用導(dǎo)致被Hold而無(wú)法被回收)。另一種就是真的Memory不夠用了,需要增加JVM的 Heap來(lái)滿足應(yīng)用程序的需求。最近有同事發(fā)的關(guān)于解決OOM的問(wèn)題,讓我了解了原來(lái)OOM除了在JVM Heap不夠時(shí)會(huì)發(fā)生,在Native Heap不夠的時(shí)候也會(huì)發(fā)生,同時(shí)JVM Heap和Native Heap存在著相互影響和平衡的關(guān)系,因此就仔細(xì)的去看了關(guān)于OOM和JVM配置優(yōu)化的內(nèi)容。
OOM
在 其他語(yǔ)言類似于C,Delphi等等由于內(nèi)存都是由自己分配和管理,因此內(nèi)存泄露的問(wèn)題比較常見,同時(shí)也是很頭痛的一件事情。而Java的對(duì)象生命周期管 理都是JVM來(lái)做的,簡(jiǎn)化了開發(fā)人員的非業(yè)務(wù)邏輯的處理,但是這種自動(dòng)管理回收機(jī)制也是基于一些規(guī)則的,而違背了這些規(guī)則的時(shí)候,就會(huì)造成所謂的 “Memory Leak”。
OOM(Java Heap)
錯(cuò)誤提示:java.lang.OutOfMemoryError。
這 類OOM是由于JVM分配的給應(yīng)用的Heap Memory已經(jīng)被耗盡,可能是因?yàn)閼?yīng)用在高負(fù)荷的情況下的卻需要很大的內(nèi)存,因此可以通過(guò)修改JVM參數(shù)來(lái)增加Java Heap Memory(不過(guò)也不能無(wú)限制增加,后面那種OOM有可能就是因?yàn)檫@個(gè)原因而產(chǎn)生)。另一種情況是因?yàn)閼?yīng)用程序使用對(duì)象或者資源沒有釋放,導(dǎo)致內(nèi)存消耗 持續(xù)增加,最后出現(xiàn)OOM,這類問(wèn)題引起的原因往往是應(yīng)用已不需要的對(duì)象還被其他有效對(duì)象所引用,那么就無(wú)法釋放,可能是業(yè)務(wù)代碼邏輯造成的(異常處理不 夠例如IO等資源),也可能是對(duì)于第三方開源項(xiàng)目中資源釋放了解不夠?qū)е率褂靡院筚Y源沒有釋放(例如JDBC的ResultSet等)。
幾個(gè)容易出現(xiàn)問(wèn)題的場(chǎng)景:
1.應(yīng)用的緩存或者Collection:如果應(yīng)用要緩存Java對(duì)象或者是在一個(gè)Collection中保存對(duì)象,那么就要確定是否會(huì)有大量的對(duì)象存入,要做保護(hù),以防止在大數(shù)據(jù)量下大量?jī)?nèi)存被消耗,同時(shí)要保證Cache的大小不會(huì)無(wú)限制增加。
2.生命周期較長(zhǎng)的對(duì)象:盡量簡(jiǎn)短對(duì)象的生命周期,現(xiàn)在采用對(duì)象的創(chuàng)建釋放代價(jià)已經(jīng)很低,同時(shí)作了很好的優(yōu)化,要比創(chuàng)建一個(gè)對(duì)象長(zhǎng)期反復(fù)使用要好。如果能夠設(shè)置超時(shí)的情景下,盡量設(shè)置超時(shí)。
3.類似于JDBC的Connection Pool,在使用Pool中的對(duì)象以后需要釋放并返回,不然就會(huì)造成Pool的不斷增大,在其他Pool中使用也是一樣。同樣ResultSet,IO這類資源的釋放都需要注意。
解決的方法就是查找錯(cuò)誤或者是增加Java Heap Memory。對(duì)于此類問(wèn)題檢測(cè)工具相當(dāng)多,這里就不做介紹了。
OOM(Native Heap)
錯(cuò)誤提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
Native Heap Memory是JVM 內(nèi)部使用的Memory,這部分的Memory可以通過(guò)JDK提供的JNI的方式去訪問(wèn),這部分Memory效率很高,但是管理需要自己去做,如果沒有把 握最好不要使用,以防出現(xiàn)內(nèi)存泄露問(wèn)題。JVM 使用Native Heap Memory用來(lái)優(yōu)化代碼載入(JTI代碼生成),臨時(shí)對(duì)象空間申請(qǐng),以及JVM內(nèi)部的一些操作。這次同事在壓力測(cè)試中遇到的問(wèn)題就是這類OOM,也就是 這類Memory耗盡。同樣這類OOM產(chǎn)生的問(wèn)題也是分成正常使用耗盡和無(wú)釋放資源耗盡兩類。無(wú)釋放資源耗盡很多時(shí)候不是程序員自身的原因,可能是引用的 第三方包的缺陷,例如很多人遇到的Oracle 9 JDBC驅(qū)動(dòng)在低版本中有內(nèi)存泄露的問(wèn)題。要確定這類問(wèn)題,就需要去觀察Native Heap Memory的增長(zhǎng)和使用情況,在服務(wù)器應(yīng)用起來(lái)以后,運(yùn)行一段時(shí)間后JVM對(duì)于Native Heap Memory的使用會(huì)達(dá)到一個(gè)穩(wěn)定的階段,此時(shí)可以看看什么操作對(duì)于Native Heap Memory操作頻繁,而且使得Native Heap Memory增長(zhǎng),對(duì)于Native Heap Memory的情況我還沒有找到辦法去檢測(cè),現(xiàn)在能夠看到的就是為JVM啟動(dòng)時(shí)候增加-verbose:jni參數(shù)來(lái)觀察對(duì)于Native Heap Memory的操作。另一種情況就是正常消耗Native Heap Memory,對(duì)于Native Heap Memory的使用主要取決于JVM代碼生成,線程創(chuàng)建,用于優(yōu)化的臨時(shí)代碼和對(duì)象產(chǎn)生。當(dāng)正常耗盡Native Heap Memory時(shí),那么就需要增加Native Heap Memory,此時(shí)就會(huì)和我們前面提到增加java Heap Memory的情況出現(xiàn)矛盾。
應(yīng)用內(nèi)存組合
對(duì) 于應(yīng)用來(lái)說(shuō),可分配的內(nèi)存受到OS的限制,不同的OS對(duì)進(jìn)程所能訪問(wèn)虛擬內(nèi)存地址區(qū)間直接影響對(duì)于應(yīng)用內(nèi)存的分配,32位的操作系統(tǒng)通常最大支持4G的內(nèi) 存尋址,而Linux一般為3G,Windows為2G。然而這些大小的內(nèi)存并不會(huì)全部給JVM的Java Heap使用,它主要會(huì)分成三部分:Java Heap,Native Heap,載入資源和類庫(kù)等所占用的內(nèi)存。那么由此可見,Native Heap和 Java Heap大小配置是相互制約的,哪一部分分配多了都可能會(huì)影響到另外一部分的正常工作,因此如果通過(guò)命令行去配置,那么需要確切的了解應(yīng)用使用情況,否則 采用默認(rèn)配置自動(dòng)監(jiān)測(cè)會(huì)更好的優(yōu)化應(yīng)用使用情況。
同樣要注意的就是進(jìn)程的虛擬內(nèi)存和機(jī)器的實(shí)際內(nèi)存還是有區(qū)別的,對(duì)于機(jī)器來(lái)說(shuō)實(shí)際內(nèi)存以及硬盤提供的虛擬內(nèi)存都是提供給機(jī)器上所有進(jìn)程使用的,因此在設(shè)置JVM參數(shù)時(shí),它的虛擬內(nèi)存絕對(duì)不應(yīng)該超過(guò)實(shí)際內(nèi)存的大小。
《二》
這 里首先要說(shuō)明的是這里提到的JVM是Sun的HotSpot JVM 5和以上的版本。性能優(yōu)化在應(yīng)用方面可以有很多手段,包括Cache,多線程,各種算法等等。通常情況下是不建議在沒有任何統(tǒng)計(jì)和分析的情況下去手動(dòng)配置 JVM的參數(shù)來(lái)調(diào)整性能,因?yàn)樵贘VM 5以上已經(jīng)作了根據(jù)機(jī)器和OS的情況自動(dòng)配置合適參數(shù)的算法,基本能夠滿足大部分的情況,當(dāng)然這種自動(dòng)適配只是一種通用的方式,如果說(shuō)真的要達(dá)到最優(yōu),那 么還是需要根據(jù)實(shí)際的使用情況來(lái)手動(dòng)的配置各種參數(shù)設(shè)置,提高性能。
JVM能夠?qū)π阅墚a(chǎn)生影響的最大部分就是對(duì)于內(nèi)存的管理。從jdk 1.5以后內(nèi)存管理和分配有了很多的改善和提高。
內(nèi)存分配以及管理的幾個(gè)基本概念和參數(shù)說(shuō)明:
Java Hotspot Mode:
server 和 client兩種模式,如果不配置,JVM會(huì)根據(jù)應(yīng)用服務(wù)器硬件配置自動(dòng)選擇模式,server模式啟動(dòng)比較慢,但是運(yùn)行期速度得到了優(yōu)化,client啟動(dòng)比較快,但是運(yùn)行期響應(yīng)沒有server模式的優(yōu)化,適合于個(gè)人PC的服務(wù)開發(fā)和測(cè)試。
Garbage Collector Policy:
在Jdk 1.5的時(shí)候已經(jīng)提供了三種GC,除了原來(lái)提供的串行GC(SerialGC)以外,還提供了兩種新的GC:ParallelGC和 ConcMarkSweepGC。ParallelGC采用了多線程并行管理和回收垃圾對(duì)象,提高了回收效率,提高了服務(wù)器的吞吐量,適合于多處理器的服 務(wù)器。ConcMarkSweepGC采用的是并發(fā)方式來(lái)管理和回收垃圾對(duì)象,降低垃圾回收產(chǎn)生的響應(yīng)暫停時(shí)間。這里說(shuō)一下并發(fā)和并行的區(qū)別,并發(fā)指的是 多個(gè)進(jìn)程并行執(zhí)行垃圾回收,那么可以很好的利用多處理器,而并行指的是應(yīng)用程序不需要暫??梢院屠厥站€程并發(fā)工作。串行GC適合小型應(yīng)用和單處理器系 統(tǒng)(無(wú)需多線程交互,效率比較高),后兩者適合大型系統(tǒng)。
使用方式就是在參數(shù)配置中增加-XX:+UseParallelGC等方式來(lái)設(shè)置。
對(duì)于這部分的配置在網(wǎng)上有很多的實(shí)例可以參考,不過(guò)最終采用哪一種GC還是要根據(jù)具體的情況來(lái)分析和選擇。
Heap:
OOM的 各種經(jīng)歷已經(jīng)讓每一個(gè)架構(gòu)師開發(fā)人員看到了了解Heap的重要性。OOM已經(jīng)是Heap的臨界點(diǎn),不得不引起注意,然而Heap對(duì)于性能的潛在影響并未被 引起重視,不過(guò)和GC配置一樣,在沒有對(duì)使用情況作仔細(xì)分析和研究的情況下,貿(mào)然的去修改Heap配置,可能適得其反,這里就來(lái)看一下Heap的一些概念 和對(duì)于性能的影響。
我們的應(yīng)用所能夠得到的最大的Heap受三部分因素的制約:數(shù)據(jù)處理 模型(32位或者64位操作系統(tǒng)),系統(tǒng)地虛擬內(nèi)存總數(shù)和系統(tǒng)的物理內(nèi)存總數(shù)。首先Heap的大小不能超過(guò)不同操作系統(tǒng)的進(jìn)程尋址范圍,當(dāng)前大部分系統(tǒng)最 高限度是4G,Windows通常是2G,Linux通常是3G。系統(tǒng)的虛擬內(nèi)存也是分配的依據(jù),首先是不能超過(guò),然后由于操作系統(tǒng)支持硬盤來(lái)做部分的虛 擬內(nèi)存,如果設(shè)置過(guò)大,那么對(duì)于應(yīng)用響應(yīng)來(lái)說(shuō)勢(shì)必有影響。再則就是要考慮同一臺(tái)服務(wù)器上運(yùn)行多個(gè)Java虛擬機(jī)所消耗的資源總合也不能超過(guò)可用資源。就和 前面OOM分析中的一樣,其實(shí)由于OS的數(shù)據(jù)處理模型的限制,機(jī)器本身的硬件內(nèi)存資源和虛擬內(nèi)存資源并不一定會(huì)匹配,那么在有限的資源下如何調(diào)整好資源分 配,對(duì)于應(yīng)用來(lái)說(shuō)尤為重要。
關(guān)于Heap的幾個(gè)參數(shù)設(shè)置:
說(shuō)了Heap的有限資源問(wèn)題以后,就來(lái)看看如何通過(guò)配置去改變JVM對(duì)于Heap的分配。下面所說(shuō)的主要是對(duì)于Java Heap的分配,那么在申請(qǐng)了Java Heap以后,剩下的可用資源就會(huì)被使用到Native Heap。
Xms: java heap初始化時(shí)的大小。默認(rèn)情況是機(jī)器物理內(nèi)存的1/64。這個(gè)主要是根據(jù)應(yīng)用啟動(dòng)時(shí)消耗的資源決定,分配少了申請(qǐng)起來(lái)會(huì)降低啟動(dòng)速度,分配多了也浪費(fèi)。
Xmx:java heap的 最大值,默認(rèn)是機(jī)器物理內(nèi)存的1/4,最大也就到1G。這個(gè)值決定了最多可用的Java Heap Memory,分配過(guò)少就會(huì)在應(yīng)用需要大量?jī)?nèi)存作緩存或者零時(shí)對(duì)象時(shí)出現(xiàn)OOM的問(wèn)題,如果分配過(guò)大,那么就會(huì)產(chǎn)生上文提到的第二類OOM。所以如何配置 還是根據(jù)運(yùn)行過(guò)程中的分析和計(jì)算來(lái)確定,如果不能確定還是采用默認(rèn)的配置。
Xmn:java heap新 生代的空間大小。在GC模型中,根據(jù)對(duì)象的生命周期的長(zhǎng)短,產(chǎn)生了內(nèi)存分代的設(shè)計(jì):青年代(內(nèi)部也分成三部分,類似于整體劃分的作用,可以通過(guò)配置來(lái)設(shè)置 比例),老年代,持久代。每一代的管理和回收策略都不相同,最為活躍的就是青年代,同時(shí)這部分的內(nèi)存分配和管理效率也是最高。通常情況下,對(duì)于內(nèi)存的申請(qǐng) 優(yōu)先在新生代中申請(qǐng),當(dāng)內(nèi)存不夠時(shí)會(huì)整理新生代,當(dāng)整理以后還是不能滿足申請(qǐng)的內(nèi)存,就會(huì)向老年代移動(dòng)一些生命周期較長(zhǎng)的對(duì)象。這種整理和移動(dòng)會(huì)消耗資 源,同時(shí)降低系統(tǒng)運(yùn)行響應(yīng)能力,因此如果青年代設(shè)置的過(guò)小,就會(huì)頻繁的整理和移動(dòng),對(duì)性能造成影響。那是否把年青代設(shè)置的越大越好,其實(shí)不然,年青代采用 的是復(fù)制搜集算法,這種算法必須停止所有應(yīng)用程序線程,服務(wù)器線程切換時(shí)間就會(huì)成為應(yīng)用響應(yīng)的瓶頸(當(dāng)然永遠(yuǎn)不用收集那么就不存在這個(gè)問(wèn)題)。老年代采用 的是串行標(biāo)記收集的方式,并發(fā)收集可以減少對(duì)于應(yīng)用的影響。
Xss:線程堆棧最大值。允許更多的虛擬內(nèi)存空間地址被Java Heap使用。
以下是sun公司的性能優(yōu)化白皮書中提到的幾個(gè)例子:
1.對(duì)于吞吐量的調(diào)優(yōu)。機(jī)器配置:4G的內(nèi)存,32個(gè)線程并發(fā)能力。
java
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-Xmx3800m -Xms3800m
配置了最大Java Heap來(lái)充分利用系統(tǒng)內(nèi)存。
-Xmn2g
創(chuàng)建足夠大的青年代(可以并行被回收)充分利用系統(tǒng)內(nèi)存,防止將短期對(duì)象復(fù)制到老年代。
-Xss128
減少默認(rèn)最大的線程棧大小,提供更多的處理虛擬內(nèi)存地址空間被進(jìn)程使用。
-XX:+UseParallelGC
采用并行垃圾收集器對(duì)年青代的內(nèi)存進(jìn)行收集,提高效率。
-XX:ParallelGCThreads=20
減少垃圾收集線程,默認(rèn)是和服務(wù)器可支持的線程最大并發(fā)數(shù)相同,往往不需要配置到最大值。
2
.嘗試采用對(duì)老年代并行收集
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m
內(nèi)存分配被減小,因?yàn)?span>ParallelOldGC會(huì)增加對(duì)于Native Heap的需求,因此需要減小Java Heap來(lái)滿足需求。
-XX:+UseParallelOldGC
采用對(duì)于老年代并發(fā)收集的策略,可以提高收集效率。
3
.提高吞吐量,減少應(yīng)用停頓時(shí)間
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
選擇了并發(fā)標(biāo)記交換收集器,它可以并發(fā)執(zhí)行收集操作,降低應(yīng)用停止時(shí)間,同時(shí)它也是并行處理模式,可以有效地利用多處理器的系統(tǒng)的多進(jìn)程處理。
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31
表示在青年代中Eden和Survivor比例,設(shè)置增加了Survivor的大小,越大的survivor空間可以允許短期對(duì)象盡量在年青代消亡。
-XX:TargetSurvivorRatio=90
允許90%的空間被占用,超過(guò)默認(rèn)的50%,提高對(duì)于survivor的使用率。
類似的例子網(wǎng)上很多,這兒就不在列下來(lái)了,最終是否采取自己配置來(lái)替換默認(rèn)配置還是要根據(jù)虛擬機(jī)的使用情況來(lái)分析和配置。
轉(zhuǎn)自:http://blog.csdn.net/cenwenchu79/archive/2008/01/22/2059553.aspx