關於內存溢出,咱再聊點有意思的?

概述

上篇文章講了JVM在GC上的一個設計缺陷,揪出一個致使GC慢慢變長的JVM設計缺陷,可能有很多人仍是沒怎麼看明白的,今天準備講的你們應該都很容易看明白java

本文其實很猶豫寫不寫,由於感受沒有太多值得探索的東西,不過文末估計會給你點小驚喜數組

或許你們曾經都碰到過HashMap由於其非線程安全的多線程併發操做致使cpu飆高的問題,不過這個問題在JDK8裏已經解決掉了,其根本緣由網上也早已遍地開花,因此我這篇文章裏就再也不熬述了,不瞭解的能夠去網上找找相關文章,本文和你們聊的是看到的另一個現象—-內存溢出安全

現象

同事丟了一個連接過來,是內存分析的,我看到一個線程佔用的內存很是高,這個問題其實已然很是明顯了,展開看了下線程棧image.png多線程

正在調用一個Map對象的toString方法,直到拋出java.lang.OutOfMemoryError,之因此這個棧頂能看到OutOfMemoryError的邏輯是由於配置了-XX:+HeapDumpOnOutOfMemoryError參數不過這個參數只會生效一次,不會每次OOM的時候都作內存dump,你們能夠想像一下,若是是代碼的問題會發生連續的OOM,那連續作dump也不必,因而JVM裏控制這個參數只會在第一次發生OOM的時候作一次內存dump併發

分析

其實在我看到這個OutOfMemoryError棧以後,還沒等同事說多少話,我就立馬要同事去看我以前那篇關於OOM的文章了,想表達的是雖然這個線程棧裏看到了OOM,可是內存泄露其實不必定是和這個線程有關的,可能只是臨門一腳而已,不事後面細看了下這個線程佔的內存其實真的挺高了,高達2G多,因此就這個案例來講仍是和這個線程有關的,有時候不能太相信本身的經驗,具體問題仍是得具體分析纔好性能

那爲何這個線程會佔用這麼大的內存呢?看到整個棧後面都在作字符串的拼接擴容動做,由於都是toString方法觸發的,難道真的有個2G的字符串?詢問同事他們說絕對不可能存在這麼大的字符串,貌似老早以前有同事問過我相似的問題,不過我都一直懷疑他們說的,以爲確定是存在這麼大的字符串的,只是他們不知道而已,原來那個問題我也已經忘記最後狀況了。今天又有相似的問題過來,我想也許我想的真的不對?後面同事打我電話說了下場景,他打印一個Map,可是這個Map實際上是一個ConcurrentHashMap,是線程安全的,可是這個map裏的value是一個HashSet,這個HashSet是非線程安全的,而且存在多個線程修改這個Set的狀況,那會不會是由於併發致使的呢,HashSet裏其實就是一個HasMap的結構,我以爲是頗有可能的,因而要同事本身去模擬下這個場景,看可否重現出來ui

我繼續看他們的內存dump,果真發現了一些貓膩,確實在打印那個HashSet過程當中,next字段是循環連起來的,因而基本肯定了死循環的存在,沒過一下子,同事也重現出來了,大概邏輯以下:線程

image.png

注意,這個得在JDK6或者7下跑纔會重現,JDK8下不存在這個問題設計

Demo裏就是兩個線程同時對HashSet進行修改,可能帶來的一個後果是裏面的HashMap由於要擴容而且作rehash而出現死循環的狀況,當有線程要打印這個HashSet的時候,會調用其toString方法,再看看其父類AbstractCollection的toString的邏輯:3d

image.png

就是挨個遍歷,而後將值塞到StringBuilder裏,若是正巧以前由於多線程的併發操做致使了死循環鏈的產生,那可能會致使這個StringBuilder會很是大,而且還會不斷進行擴容,正如上面的堆棧看到的同樣,這直接帶來的一個後果就是出現內存溢出

內存富餘下的OutOfMemory

對於同事線上碰到的那個問題看到的OOM提示是Requested array size exceeds VM limit,這個提示講真我仍是第一次碰到有發生的,假如說你的內存其實很是大,足夠的剩餘,可是當你要建立一個數組的時候,若是你的數組的長度超過Integer.MAX_VALUE-2的話,那你將會看到一個這個提示的OOM拋出來,其實這也是你能建立的數組的最大長度了,這或許不少人都沒有注意到的,就把這個當作本文的一個最有價值的亮點吧

歡迎關注 PerfMa 社區,推薦閱讀:
JVM Code Cache空間不足,致使服務性能變慢
海量鏈接服務端CMS調優記

相關文章
相關標籤/搜索