如果沒遇到過OME錯誤,都不好意思說自己是做Java開發(fā)的。

最近更新文章的速度很慢,懶,另外我對時間的分配不太擅長,事情一旦多起來,就很容易焦頭爛額,效率也變低,看起來一天忙忙碌碌,最后發(fā)現(xiàn)處理的事情并不多。但是在不多的事情中,每天其實在程序開發(fā)上都會遇到一些值得分享的事情,如果一直沒有動筆記錄,很多事情都慢慢的忘記了。所以不管怎么忙,我想還是把工作中遇到技術上的問題,拿出來分享給大家,幫大家少踩坑。也是自己工作的一次記錄。
OME的發(fā)生
OutOfMemoryError異常可以說是一個比較棘手的問題,Java中所有的對象都存儲在堆中,通常如果JVM無法再分配新的內(nèi)存,內(nèi)存耗盡,垃圾回收無法及時回收內(nèi)存,就會拋出OutOfMemoryError。
我這次遇到的OME錯誤如圖:

報錯誤的原因是因為: 執(zhí)行垃圾收集的時間比例太大, 有效的運算量太小. 默認情況下, 如果GC花費的時間超過 98%, 并且GC回收的內(nèi)存少于 2%, JVM就會拋出這個錯誤。
解決方案
都是自己寫的程序,報這個錯誤,自己心里能沒有點數(shù)嗎,我里面寫了一個大對象,實例化之后會不斷的加載網(wǎng)絡數(shù)據(jù)到內(nèi)存中,并且我沒有銷毀這個對象,繼續(xù)使用這個對象,同時設置了線程一直讓此對象進行等待。
然后我修改大對象在使用之后指向null。壓力就沒這么大了。
JVM內(nèi)存區(qū)域
可能對JVM了解的不夠清晰,下面再整理下JVM的一些常用知識點。
JVM的自動內(nèi)存管理可以歸結為兩個問題:
- 給對象分配內(nèi)存
- 回收分配的內(nèi)存
經(jīng)常變動堆內(nèi)存區(qū)域為:新生代(Eden),存活區(qū)(Survivor),老年代(Old)
Perm Gen:為持久帶,主要存放Java類的類信息,與垃圾收集要收集的Java對象關系
不大。
Code Cache:主要存放代碼緩存,它主要用于存放JIT所編譯的代碼,JIT編譯器是在程序運行期間,將Java字節(jié)碼編譯成平臺相關的二進制代碼。正因為此編譯行為發(fā)生在程序運行期間,所以該編譯器被稱為Just-In-Time編譯器。JIT主要編譯的是熱點代碼。

關鍵點:
- 大多數(shù)情況下,對象在新生代Eden區(qū)中分配,Eden區(qū)域不夠的時候,虛擬機發(fā)動一次Minor GC,對象在發(fā)生Minor GC后仍能存活,那么對象將被移動到Suvivor空間中,每經(jīng)過一次Minor GC,對象的年齡增加1歲,增加到一定的年齡(默認15歲),就會晉升到老年代Old Gen。
- 大對象是指需要連續(xù)內(nèi)存空間的Java對象,典型的大對象是那種很長的字符串及數(shù)組。大對象對內(nèi)存分配來說就是一個壞消息。 像爬蟲爬取的整個頁面,分配成字符串就是占用內(nèi)存很多的字符串。
- 在發(fā)生MinorGC之前,虛擬機會先檢查老年代的最大可用連續(xù)空間十分大于新生代的所有對象空間,如果成了,那么Minor GC就是安全的。如果不是,那么檢查HandlePromotionFailure是否允許擔保失敗,如果允許,那么檢查老年代的最大可用空間是否大于之前晉升到老年代對象的平均大小,如果大于那么也嘗試進行一次Minor GC。 如果小于,或者HandlePromotionFailure設置為不允許冒險,那么改成進行一次Minor GC。
兩張Jconsole的JVM圖:


常見的JVM設置參數(shù)
可以在命令行直接查看可以設置的參數(shù):



-XX:NewRatio:年輕代(包括Eden和兩個Survivor區(qū))與年老代的比值(除去持久代)
-XX:SurvivorRatio:Eden區(qū)與Survivor區(qū)的大小比值
-XX:+DisableExplicitGC 關閉System.gc()
-XX:MaxTenuringThread 對象在Suvivor區(qū)域中的年齡到達多少進入Old Gen
-XX:PretenureSizeThreshold 另大于這個設置值的對象直接在老年代分配,避免Eden區(qū)和Suvivor區(qū)域之間大量的內(nèi)存分配
這篇文章里面也列舉了不少參數(shù):
https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
最后
不斷的進步。希望能幫助到大家。