在Java虛擬機規范的描述中,除了程式計數器外,虛擬機記憶體的其他幾個運行時區域都有發生OutOfMemoryError例外的可能,
1.Java堆溢位
Java堆用于存盤物件實體,只要不斷地創建物件,并且保證GC Roots到物件之間有可達路徑來避免垃圾回識訓制清除這些物件,那么在物件數量到達最大堆的容量限制后就會產生記憶體溢位例外,
Java堆記憶體的OOM例外是實際應用中常見的記憶體溢位例外情況,
要解決這個區域的例外,一般的手段是先通過記憶體映像分析工具對Dump出來的堆轉儲快照進行分析,重點是確認記憶體中的物件是否是必要的,也就是要先分清楚到底是出現了記憶體泄漏還是記憶體溢位,
如果是記憶體泄露,可進一步通過工具查看泄露物件到GC Roots的參考鏈,于是就能找到泄露物件是通過怎樣的路徑與GCRoots相關聯并導致垃圾收集器無法自動回收它們的,掌握了泄露物件的型別資訊及GCRoots參考鏈的資訊,就可以比較準確地定位出泄露代碼的位置,
如果不存在泄露,換句話說,就是記憶體中的物件確實都還必須存活著,那就應當檢查虛擬機的堆引數(-Xmx 與-Xms),與機器物理記憶體對比看是否還可以調大,從代碼上檢查是否存在某些物件生命周期過長、持有狀態時間過長的情況,嘗試減少程式運行期的記憶體,
2.虛擬機堆疊和本地方法堆疊溢位
對于HotSpot來說,雖然-Xoss引數(設定本地方法堆疊大小)存在,但實際上是無效的,堆疊容量只由-Xss 引數設定,
關于虛擬機堆疊和本地方法堆疊,在Java虛擬機規范中描述了兩種例外:
- 如果執行緒請求的堆疊深度大于虛擬機所允許的最大深度,將拋出StackOverflowError例外,
- 如果虛擬機在擴展堆疊時無法申請到足夠的記憶體空間,則拋出OutOfMemoryError 例外,
在單個執行緒下,無論是由于堆疊幀太大還是虛擬機堆疊容量太小,當記憶體無法分配的時候,虛擬機拋出的都是StackOverflowError例外,
不斷的創建執行緒的方式倒是可以產生記憶體溢位例外,
如果使用虛擬機默認引數,堆疊深度在大多數情況下達到1000 ~ 2000完全沒有問題,對于正常的方法呼叫(包括遞回),這個深度應該完全夠用了, 但是,如果是建立過多執行緒導致的記憶體溢位,在不能減少執行緒數或者更換64位虛擬機的情況下,就只能通過減少最大堆和減少堆疊容量來換取更多的執行緒,如果沒有這方面的處理經驗,這種通過“減少記憶體”的手段來解決記憶體溢位的方式會比較難以想到,
3.方法區和運行時常量池溢位
方法區溢位也是一種常見的記憶體溢位例外,一個類要被垃圾收集器回收掉,判定條件是比較苛刻的,在經常動態生成大量Class的應用中,需要特別注意類的回收狀況,這類場景除了程式使用了CGLib位元組碼增強和動態語言之外,常見的還有大量JSP或動態產生JSP檔案的應用(JSP 第一次運行時需要編譯為Java類)、基于OSGi的應用(即使是同一個類檔案,被不同的加載器加載也會視為不同的類)等,
4.本機直接記憶體溢位
DirectMemory容量可通過-XX : MaxDirectMemorySize 指定,如果不指定,則默認與Java堆最大值(-Xmx指定) 一樣,
雖然使用DirectByteBuffer分配記憶體也會拋出記憶體溢位例外,但它拋出例外時并沒有真正向作業系統申請分配記憶體,而是通過計算得知記憶體無法分配,于是手動拋出例外,
由DirectMemory導致的記憶體溢位,一個明顯的特征是在Heap Dump檔案中不會看見明顯的例外,如果讀者發現O0M之后Dump檔案很小,而程式中又直接或間接使用了NIO,那就可以考慮檢查一下是不是這方面的原因,
-- 參考資料:
《深入理解Java虛擬機--JVM高級特性與最佳實踐 第2版》 --周志明 著
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/125378.html
標籤:Java
下一篇:JVM記憶體區域
