OOM這個縮寫就是Java程序開發過程當中讓人最頭痛的問題:Out of
Memory。在不少開發人員的開發過程當中,或多或少的都會遇到這類問題,這類問題定位比較困難,每每須要根據經驗來判斷可能出現問題的代碼。緣由主要是
兩個:對象沒有被釋放(多種狀況引發,每每是比較隱蔽的引用致使被Hold而沒法被回收)。另外一種就是真的Memory不夠用了,須要增長JVM的
Heap來知足應用程序的需求。最近有同事發的關於解決OOM的問題,讓我瞭解了原來OOM除了在JVM Heap不夠時會發生,在Native
Heap不夠的時候也會發生,同時JVM Heap和Native
Heap存在着相互影響和平衡的關係,所以就仔細的去看了關於OOM和JVM配置優化的內容。
OOM
在
其餘語言相似於C,Delphi等等因爲內存都是由本身分配和管理,所以內存泄露的問題比較常見,同時也是很頭痛的一件事情。而Java的對象生命週期管
理都是JVM來作的,簡化了開發人員的非業務邏輯的處理,可是這種自動管理回收機制也是基於一些規則的,而違背了這些規則的時候,就會形成所謂的
「Memory Leak」。
OOM(Java Heap)
錯誤提示:java.lang.OutOfMemoryError。
這 類OOM是因爲JVM分配的給應用的Heap
Memory已經被耗盡,多是由於應用在高負荷的狀況下的卻須要很大的內存,所以能夠經過修改JVM參數來增長Java Heap
Memory(不過也不能無限制增長,後面那種OOM有可能就是由於這個緣由而產生)。另外一種狀況是由於應用程序使用對象或者資源沒有釋放,致使內存消耗
持續增長,最後出現OOM,這類問題引發的緣由每每是應用已不須要的對象還被其餘有效對象所引用,那麼就沒法釋放,多是業務代碼邏輯形成的(異常處理不
夠例如IO等資源),也多是對於第三方開源項目中資源釋放了解不夠致使使用之後資源沒有釋放(例如JDBC的ResultSet等)。
幾個容易出現問題的場景:
1.應用的緩存或者Collection:若是應用要緩存Java對象或者是在一個Collection中保存對象,那麼就要肯定是否會有大量的對象存入,要作保護,以防止在大數據量下大量內存被消耗,同時要保證Cache的大小不會無限制增長。
2.生命週期較長的對象:儘可能簡短對象的生命週期,如今採用對象的建立釋放代價已經很低,同時做了很好的優化,要比建立一個對象長期反覆使用要好。若是可以設置超時的情景下,儘可能設置超時。
3.相似於JDBC的Connection Pool,在使用Pool中的對象之後須要釋放並返回,否則就會形成Pool的不斷增大,在其餘Pool中使用也是同樣。一樣ResultSet,IO這類資源的釋放都須要注意。
解決的方法就是查找錯誤或者是增長Java Heap Memory。對於此類問題檢測工具至關多,這裏就不作介紹了。
OOM(Native Heap)
錯誤提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
Native Heap Memory是JVM
內部使用的Memory,這部分的Memory能夠經過JDK提供的JNI的方式去訪問,這部分Memory效率很高,可是管理須要本身去作,若是沒有把
握最好不要使用,以防出現內存泄露問題。JVM 使用Native Heap
Memory用來優化代碼載入(JTI代碼生成),臨時對象空間申請,以及JVM內部的一些操做。此次同事在壓力測試中遇到的問題就是這類OOM,也就是
這類Memory耗盡。一樣這類OOM產生的問題也是分紅正常使用耗盡和無釋放資源耗盡兩類。無釋放資源耗盡不少時候不是程序員自身的緣由,多是引用的
第三方包的缺陷,例如不少人遇到的Oracle 9 JDBC驅動在低版本中有內存泄露的問題。要肯定這類問題,就須要去觀察Native Heap
Memory的增加和使用狀況,在服務器應用起來之後,運行一段時間後JVM對於Native Heap
Memory的使用會達到一個穩定的階段,此時能夠看看什麼操做對於Native Heap Memory操做頻繁,並且使得Native Heap
Memory增加,對於Native Heap
Memory的狀況我尚未找到辦法去檢測,如今可以看到的就是爲JVM啓動時候增長-verbose:jni參數來觀察對於Native Heap
Memory的操做。另外一種狀況就是正常消耗Native Heap Memory,對於Native Heap
Memory的使用主要取決於JVM代碼生成,線程建立,用於優化的臨時代碼和對象產生。當正常耗盡Native Heap
Memory時,那麼就須要增長Native Heap Memory,此時就會和咱們前面提到增長java Heap Memory的狀況出現矛盾。
應用內存組合
對
於應用來講,可分配的內存受到OS的限制,不一樣的OS對進程所能訪問虛擬內存地址區間直接影響對於應用內存的分配,32位的操做系統一般最大支持4G的內
存尋址,而Linux通常爲3G,Windows爲2G。然而這些大小的內存並不會所有給JVM的Java
Heap使用,它主要會分紅三部分:Java Heap,Native Heap,載入資源和類庫等所佔用的內存。那麼因而可知,Native
Heap和 Java
Heap大小配置是相互制約的,哪一部分分配多了均可能會影響到另一部分的正常工做,所以若是經過命令行去配置,那麼須要確切的瞭解應用使用狀況,不然
採用默認配置自動監測會更好的優化應用使用狀況。
一樣要注意的就是進程的虛擬內存和機器的實際內存仍是有區別的,對於機器來講實際內存以及硬盤提供的虛擬內存都是提供給機器上全部進程使用的,所以在設置JVM參數時,它的虛擬內存絕對不該該超過實際內存的大小。
《二》
這 裏首先要說明的是這裏提到的JVM是Sun的HotSpot JVM
5和以上的版本。性能優化在應用方面能夠有不少手段,包括Cache,多線程,各類算法等等。一般狀況下是不建議在沒有任何統計和分析的狀況下去手動配置
JVM的參數來調整性能,由於在JVM
5以上已經做了根據機器和OS的狀況自動配置合適參數的算法,基本可以知足大部分的狀況,固然這種自動適配只是一種通用的方式,若是說真的要達到最優,那
麼仍是須要根據實際的使用狀況來手動的配置各類參數設置,提升性能。
JVM可以對性能產生影響的最大部分就是對於內存的管理。從jdk 1.5之後內存管理和分配有了不少的改善和提升。
內存分配以及管理的幾個基本概念和參數說明:
Java Hotspot Mode:
server 和 client兩種模式,若是不配置,JVM會根據應用服務器硬件配置自動選擇模式,server模式啓動比較慢,可是運行期速度獲得了優化,client啓動比較快,可是運行期響應沒有server模式的優化,適合於我的PC的服務開發和測試。
Garbage Collector Policy:
在Jdk
1.5的時候已經提供了三種GC,除了原來提供的串行GC(SerialGC)之外,還提供了兩種新的GC:ParallelGC和
ConcMarkSweepGC。ParallelGC採用了多線程並行管理和回收垃圾對象,提升了回收效率,提升了服務器的吞吐量,適合於多處理器的服
務器。ConcMarkSweepGC採用的是併發方式來管理和回收垃圾對象,下降垃圾回收產生的響應暫停時間。這裏說一下併發和並行的區別,併發指的是
多個進程並行執行垃圾回收,那麼能夠很好的利用多處理器,而並行指的是應用程序不須要暫停能夠和垃圾回收線程併發工做。串行GC適合小型應用和單處理器系
統(無需多線程交互,效率比較高),後二者適合大型系統。
使用方式就是在參數配置中增長-XX:+UseParallelGC等方式來設置。
對於這部分的配置在網上有不少的實例能夠參考,不過最終採用哪種GC仍是要根據具體的狀況來分析和選擇。
Heap:
OOM的
各類經歷已經讓每個架構師開發人員看到了瞭解Heap的重要性。OOM已是Heap的臨界點,不得不引發注意,然而Heap對於性能的潛在影響並未被
引發重視,不過和GC配置同樣,在沒有對使用狀況做仔細分析和研究的狀況下,貿然的去修改Heap配置,可能拔苗助長,這裏就來看一下Heap的一些概念
和對於性能的影響。
咱們的應用所可以獲得的最大的Heap受三部分因素的制約:數據處理
模型(32位或者64位操做系統),系統地虛擬內存總數和系統的物理內存總數。首先Heap的大小不能超過不一樣操做系統的進程尋址範圍,當前大部分系統最
高限度是4G,Windows一般是2G,Linux一般是3G。系統的虛擬內存也是分配的依據,首先是不能超過,而後因爲操做系統支持硬盤來作部分的虛
擬內存,若是設置過大,那麼對於應用響應來講勢必有影響。再則就是要考慮同一臺服務器上運行多個Java虛擬機所消耗的資源總合也不能超過可用資源。就和
前面OOM分析中的同樣,其實因爲OS的數據處理模型的限制,機器自己的硬件內存資源和虛擬內存資源並不必定會匹配,那麼在有限的資源下如何調整好資源分
配,對於應用來講尤其重要。
關於Heap的幾個參數設置:
說了Heap的有限資源問題之後,就來看看如何經過配置去改變JVM對於Heap的分配。下面所說的主要是對於Java Heap的分配,那麼在申請了Java Heap之後,剩下的可用資源就會被使用到Native Heap。
Xms: java heap初始化時的大小。默認狀況是機器物理內存的1/64。這個主要是根據應用啓動時消耗的資源決定,分配少了申請起來會下降啓動速度,分配多了也浪費。
Xmx:java heap的
最大值,默認是機器物理內存的1/4,最大也就到1G。這個值決定了最多可用的Java Heap
Memory,分配過少就會在應用須要大量內存做緩存或者零時對象時出現OOM的問題,若是分配過大,那麼就會產生上文提到的第二類OOM。因此如何配置
仍是根據運行過程當中的分析和計算來肯定,若是不能肯定仍是採用默認的配置。
Xmn:java heap新
生代的空間大小。在GC模型中,根據對象的生命週期的長短,產生了內存分代的設計:青年代(內部也分紅三部分,相似於總體劃分的做用,能夠經過配置來設置
比例),老年代,持久代。每一代的管理和回收策略都不相同,最爲活躍的就是青年代,同時這部分的內存分配和管理效率也是最高。一般狀況下,對於內存的申請
優先在新生代中申請,當內存不夠時會整理新生代,當整理之後仍是不能知足申請的內存,就會向老年代移動一些生命週期較長的對象。這種整理和移動會消耗資
源,同時下降系統運行響應能力,所以若是青年代設置的太小,就會頻繁的整理和移動,對性能形成影響。那是否把年青代設置的越大越好,其實否則,年青代採用
的是複製蒐集算法,這種算法必須中止全部應用程序線程,服務器線程切換時間就會成爲應用響應的瓶頸(固然永遠不用收集那麼就不存在這個問題)。老年代採用
的是串行標記收集的方式,併發收集能夠減小對於應用的影響。
Xss:線程堆棧最大值。容許更多的虛擬內存空間地址被Java Heap使用。
如下是sun公司的性能優化白皮書中提到的幾個例子:
1.對於吞吐量的調優。機器配置:4G的內存,32個線程併發能力。
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-Xmx3800m -Xms3800m 配置了最大Java Heap來充分利用系統內存。
-Xmn2g 建立足夠大的青年代(能夠並行被回收)充分利用系統內存,防止將短時間對象複製到老年代。
-Xss128 減小默認最大的線程棧大小,提供更多的處理虛擬內存地址空間被進程使用。
-XX:+UseParallelGC 採用並行垃圾收集器對年青代的內存進行收集,提升效率。
-XX:ParallelGCThreads=20 減小垃圾收集線程,默認是和服務器可支持的線程最大併發數相同,每每不須要配置到最大值。
2.嘗試採用對老年代並行收集
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m 內存分配被減少,由於ParallelOldGC會增長對於Native Heap的需求,所以須要減少Java Heap來知足需求。
-XX:+UseParallelOldGC 採用對於老年代併發收集的策略,能夠提升收集效率。
3.提升吞吐量,減小應用停頓時間
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 選擇了併發標記交換收集器,它能夠併發執行收集操做,下降應用中止時間,同時它也是並行處理模式,能夠有效地利用多處理器的系統的多進程處理。 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31 表示在青年代中Eden和Survivor比例,設置增長了Survivor的大小,越大的survivor空間能夠容許短時間對象儘可能在年青代消亡。 -XX:TargetSurvivorRatio=90 容許90%的空間被佔用,超過默認的50%,提升對於survivor的使用率。 相似的例子網上不少,這兒就不在列下來了,最終是否採起本身配置來替換默認配置仍是要根據虛擬機的使用狀況來分析和配置。