01. 怎麼回事?
納尼,Java 不是自動管理記憶體嗎?怎麼可能會出現記憶體洩洩洩洩洩洩漏!
Java 最牛逼的一個特性就是垃圾回收機制,不用像 C++ 需要手動管理記憶體,所以作為 Java 程式設計師很幸福,只管 New New New 即可,反正 Java 會自動回收過期的物件。。。
那麼 Java 都自動管理記憶體了,那怎麼會出現記憶體洩漏,難道 Jvm 有 bug? 不要急,且聽我慢慢道來。。
02. 怎麼判斷可以被回收
先了解一下 Jvm 是怎麼判斷一個物件可以被回收。一般有兩種方式,一種是引用計數法,一種是可達性分析。
引用計數法:每個物件有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。
這個辦法看起來挺簡單的,但是如果出現 A 引用了 B,B 又引用了 A,這時候就算他們都不再使用了,但因為相互引用 計算器=1 永遠無法被回收。
此方法簡單,無法解決物件相互迴圈引用的問題。
可達性分析(Reachability Analysis):從 GC Roots 開始向下搜尋,搜尋所走過的路徑稱為引用鏈。當一個物件到 GC Roots 沒有任何引用鏈相連時,則證明此物件是不可用的,那麼虛擬機器就判斷是可回收物件。
可達性分析可以解決迴圈引用的問題。
那麼 gc roots 物件是哪些呢
- 虛擬機器棧中引用的物件
- 方法區中類靜態屬性引用的物件
- 方法區中常量引用的物件
- 本地方法棧中JNI[即一般說的Native]引用的物件
目前主流的虛擬機器中大多使用可達性分析的方式來判定物件是否可被 GC 回收。
03. 什麼情況下會出現記憶體洩漏
既然可達性分析好像已經很牛逼的樣子了,怎麼可能還會出現記憶體洩漏呢,那我們再來看一下記憶體洩漏的定義。
記憶體洩露就是指一個不再被程式使用的物件或變數一直被佔據在記憶體中。
有可能此物件已經不使用了,但是還有其它物件保持著此物件的引用,就會導致 GC 不能回收此物件,這種情況下就會出現記憶體洩漏。
寫一個程式讓出現記憶體洩漏
①長生命週期的物件持有短生命週期物件的引用就很可能發生記憶體洩露,儘管短生命週期物件已經不再需要,但是因為長生命週期物件持有它的引用而導致不能被回收。
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他程式碼
}
}
複製程式碼
這裡的 object 例項,其實我們期望它只作用於 method1() 方法中,且其他地方不會再用到它,但是,當method1()方法執行完成後,object 物件所分配的記憶體不會馬上被認為是可以被釋放的物件,只有在 Simple 類建立的物件被釋放後才會被釋放,嚴格的說,這就是一種記憶體洩露。
解決方法就是將 object 作為 method1() 方法中的區域性變數。
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他程式碼
object = null;
}
}
複製程式碼
當然大家有可能會想就這一個方法也不會有多大影響,但如果在某些專案中,一個方法在一分鐘之內呼叫上萬次的時候,就會出現很明顯的記憶體洩漏現象。
②集合中的記憶體洩漏,比如 HashMap、ArrayList 等,這些物件經常會發生記憶體洩露。比如當它們被宣告為靜態物件時,它們的生命週期會跟應用程式的生命週期一樣長,很容易造成記憶體不足。
下面給出了一個關於集合記憶體洩露的例子。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
//此時,所有的Object物件都沒有被釋放,因為變數v引用這些物件。
複製程式碼
在這個例子中,我們迴圈申請 Object 物件,並將所申請的物件放入一個 Vector 中,如果我們僅僅釋放引用本身,那麼 Vector 仍然引用該物件,所以這個物件對 GC 來說是不可回收的。
因此,如果物件加入到 Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 物件設定為 null。
以上兩種是最常見的記憶體洩漏案例。當然還有一些記憶體洩漏的例子,這裡就不再一一例舉了,感興趣的同學可以在網上找找資料。
04. 記憶體洩漏和記憶體溢位
很多同學總是搞不清楚,記憶體洩漏和記憶體溢位的區別,它倆是兩個完全不同的概念, 它們之間存在一些關聯。
記憶體溢位 out of memory,是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現 out of memory;
記憶體洩露 memory leak,是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被佔光。
所以記憶體洩漏可能會導致記憶體溢位,但記憶體溢位並不完全都是因為記憶體洩漏,也有可能使用了太多的大物件導致。
05. 如何檢測記憶體洩漏
最後一個重要的問題,就是如何檢測 Java 的記憶體洩漏。目前,我們通常使用一些工具來檢查 Java 程式的記憶體洩漏問題。
市場上已有幾種專業檢查 Java 記憶體洩漏的工具,它們的基本工作原理大同小異,都是通過監測 Java 程式執行時,所有物件的申請、釋放等動作,將記憶體管理的所有資訊進行統計、分析、視覺化。開發人員將根據這些資訊判斷程式是否有記憶體洩漏問題。
這些工具包括 Plumbr 、Eclipse Memory Analyzer、JProbe Profiler、JVisualVM 等。
06. 最後
以上內容其實是我曾經經常面試的內容之一,通過一系列的問題考察 Java 程式設計師對 Jvm 的理解。
比如我通常會問面試者,Java 中存在記憶體洩漏嗎?大部分人都會回答存在,接著我會問如果讓你寫一個程式讓記憶體洩漏,你會怎麼寫?大部分程式設計師就回答不上來了。
如果面試者可以回答上面的問題,我會接著和麵試者聊聊,記憶體洩漏和記憶體溢位他們之間是否存在聯絡 、以及在日常工作中如何避免寫出記憶體洩漏的程式碼 、如果生產出現 Jvm 相關問題時,排查問題的思路和步驟等等。
這些問題在我的部落格中都有答案,早些年寫了一系列關於 Jvm 的文章,大家如果感興趣的話接下來繼續去閱讀,www.ityouknow.com/java.html。
如果大家覺得在手機上看著更方便,可以關注:Java 極客技術公號,已經輸出了一些 JVM 文章,我部落格中的 Jvm 系列文章也都會推送到這個公號中。
關注一下又不會懷孕
參考出處: