本文章爲《深入淺出 Java 虛擬機》系列課程學習筆記,侵刪。學習地址爲 深入淺出 Java 虛擬機
1 JVM 中如何進行垃圾回收?
JVM 的 GC 動作並不受程序控制,它會在滿足條件的時候,自動觸發。
在發生 GC 的時候,一個對象,JVM 總能夠找到引用它的祖先。找到最後,如果發現這個祖先已經名存實亡了,它們都會被清理掉。而能夠躲過垃圾回收的那些祖先,比較特殊,它們的名字就叫作 GC Roots。
從 GC Roots 向下追溯、搜索,會產生一個叫作 Reference Chain 的鏈條。當一個對象不能和任何一個 GC Root 產生關係時,就會被清除。
2 什麼是 GC Roots?
GC Roots 是活躍的引用,可以理解爲程序通過引用可以訪問到的對象。一般分爲以下三類:
- 活動線程相關的各種引用,例如線程中與棧幀相關的各種引用(被調用的方法的引用類型參數、局部變量、臨時值)
- 類的靜態變量的引用
- JNI 引用
注意,我們這裏指的是活躍的引用,而不是對象,對象是不能作爲 GC Roots 的。
3 引用級別
- 強引用:即使程序會異常終止,這種對象也不會被回收。這種引用屬於最普通最強硬的一種存在,只有在和 GC Roots 斷絕關係時,纔會被消滅掉。例如
Object obj = new Object();
- 軟引用:軟引用用於維護一些可有可無的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,纔會拋出內存溢出異常。軟引用適用於緩存技術上
- 弱引用:弱引用對象相比較軟引用,要更加無用一些,它擁有更短的生命週期。當 JVM 進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象。它的應用場景和軟引用類似,可以在一些對內存更加敏感的系統裏採用
- 虛引用:如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收,主要用來跟蹤對象被垃圾回收的活動。虛引用必須與一個引用隊列關聯,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象之前,把這個虛引用加入到與之關聯的引用隊列中。程序如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動
4 OOM
OOM 即 Out Of Memory。當內存空間不足,系統撐不住了,JVM 就會拋出 OutOfMemoryError 錯誤。內存區域有哪些會發生 OOM 呢?
除了程序計數器,其他區域都有 OOM 溢出的可能。但是最常見的還是發生在堆上。
爲什麼會引起 OOM 呢?主要原因如下:
- 內存的容量太小了,需要擴容,或者需要調整堆的空間
- 錯誤的引用方式,發生了內存泄漏。沒有及時的切斷與 GC Roots 的關係。比如線程池裏的線程,在複用的情況下忘記清理 ThreadLocal 的內容
- 接口沒有進行範圍校驗,外部傳參超出範圍。比如數據庫查詢時的每頁條數等
- 對堆外內存無限制的使用。這種情況一旦發生更加嚴重,會造成操作系統內存耗盡
典型的內存泄漏場景,原因在於對象沒有及時的釋放自己的引用。比如一個局部變量,被外部的靜態集合引用。