實戰項目中Java heap space錯誤的解決


部標GPS通訊系統在上線之后,經過不斷調試,終於穩定運行一段時間,后來又遇到了Java heap space錯誤異常!日志如下:

 

說明系統中有未釋放的對象。如何找出這些未釋放對象以及監控JVM堆內存,優化代碼釋放內存對象呢?還有JVM的垃圾回收機制是如何運作的呢?

首先在系統啟動運行的時候打開記錄GC詳細信息,運行腳本如下:

 看看GC詳細日志,當GC到13400多次的時候新生代和老生代的堆內存幾乎用滿了,頻繁觸發Full GC (Ergonomics) ,直到沒有內存空間給新生對象了。所以JVM拋出了內存溢出錯誤!進而導致程序崩潰。

 

首先簡單了解下JVM垃圾回收機制:

從上圖可以看出 GC 的主要收集區域,包括 PSYoungGen(年輕代)、ParOldGen(老年代)、Metaspace(元數據區)。

新生代:新創建的對象都是用新生代分配內存,Eden空間不足時,觸發Minor GC,這時會把存活的對象轉移進幸存者Survivor區。
老年代:老年代用於存放經過多次Minor GC之后依然存活的對象。

新生代的GC(Minor GC):新生代通常存活時間較短基於Copying算法進行回收,所謂Copying算法就是掃描出存活的對象,並復制到一塊新的完全未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。

新生代采用空閑指針的方式來控制GC觸發,指針保持最后一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從Eden到Survivor,最后到老年代。

老年代的GC(Major GC/Full GC):老年代與新生代不同,老年代對象存活的時間比較長、比較穩定,因此采用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,然后再進行回收未被標記的對象,

回收后對用空出的空間要么進行合並、要么標記出來便於下次進行分配,總之目的就是要減少內存碎片帶來的效率損耗。

附上堆結構圖:

 

至此,在已經嘗試了將Java虛擬機中Xms(初始堆大小-2G)和Xmx(最大堆大小-4G)參數調大之后也無果,只能從代碼入手, 對象一直堆積於老年代未被釋放,應該是代碼中對象未被釋放。

為了找出代碼中未被釋放的對象,這里運用到了內存監控工具jconsole和內存堆對象統計工具jmap

jconsole.exe位於bin目錄下,用法也挺簡單的,用於調優后的監控還不錯,這里留個圖不做介紹了

 

這里運用jmap(jdk自帶的jvm內存分析的工具),將程序堆對象統計出來的結果如下:

發現到代碼問題:應該是解析協議的時候CanDataItem對象不斷被new出來,使用完了以后並未被GC回收留在老生代,

 找到CanDataItem使用完成的地方做了如下修改:

修改代碼再將T808Message等類用完了的地方做了釋放,之后終於解決了問題!修改代碼以后再次使用jmap統計如下,正常了!

附 - jmap輸出中class name非自定義類的說明:

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L; reference an instance of class
S short signed short
Z boolean true or false
[ reference one array dimension,[I表示int[]

 

總結導致java.lang.OutOfMemoryError異常的常見原因有以下幾種:

  • 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
  • 集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;
  • 代碼中存在死循環或循環產生過多重復的對象實體-----(本例中);
  • 使用的第三方軟件中的BUG;
  • 啟動參數內存值設定的過小;

至此問題總算解決了,通常情況下,Java開發人員並不需要去關心JVM是如何運行的。即使不理解JVM的工作原理,也不會給開發人員帶來過多困惑。Java程序員更容易忽視基礎技術。

“蚓無爪牙之利,筋骨之強,上食埃土,下飲黃泉,用心一也。蟹六跪而二螯,非蛇蟮之穴無可寄托者,用心躁也”。對於技術人員來說,如果長期忽略自身技術的根基而去一昧地追求高層框架技術,這無疑是舍本求末的做法。

JVM的出現,為程序員屏蔽了操作系統與硬件的細節,使得程序員從諸如內存管理這樣的繁瑣任務中解放出來。但這不並等同於允許Java程序員放棄對基礎的重視。

事實上,任何平台的程序員都應當了解平台的基本特性、實現機制以及接口,這是提高自身修養的必經之路。對於Java程序員來說,我們還是需要多多了解JVM。

了解JVM的基本實現機制,不僅對於解決實際應用中諸如GC等虛擬機問題時有直接幫助,還有利於我們更好地理解語言本身。

 

轉載請注明出處!謝謝!https://www.cnblogs.com/lys_013/p/9605352.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM