更多 Java 虛擬機方面的文章,請參見文集《Java 虛擬機》
對象的狀態(tài)
- 可達狀態(tài),被引用變量引用
-
可恢復狀態(tài),沒有被引用,系統(tǒng)調用
finalize()后重新獲得了引用 -
不可達狀態(tài),沒有被引用,系統(tǒng)調用
finalize()后沒有獲得引用,GC 回收
垃圾對象的判斷
引用計數算法
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加 1,當引用失效時,計數器值就減1,任何時刻計數器都為 0 的對象就是不可能再被使用的。
引用計數算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的選擇。但 Java 語言并沒有選擇這種算法來進行垃圾回收,主要原因是它很難解決對象之間的相互循環(huán)引用問題。根搜索算法
Java 都是采用根搜索算法來判定對象是否存活的。這種算法的基本思路是通過一系列名為 GC Roots 的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連時,就證明此對象是不可用的。
GC Root,它不是對象圖里面的對象,他是一組特別管理的指針,是 GC tracing 的起點,假如兩個對象(objectA與objectB)互相引用,然后其實后面又沒有用到他們了比如objectA=null,objectB=null,此時他們到 GC Root 的路徑是斷的,只是他們互相之間是可以到達的,所以可以回收。
GC 的起點 GC Roots
- JVM 棧中 local variable 引用的對象
- 類靜態(tài)屬性 static 引用的對象
- 常量引用的對象
- 本地方法棧中 JNI 引用的對象
GC 的任務
- 分配內存
- 確保被引用的對象內存不被回收
- 確保不被引用的對象內存被回收
STW
GC 是復雜且耗時的操作,GC 時,整個應用要被暫時中止,即 STW(Stop the World),因為 GC 需要更新整個應用中所有對象的實際內存地址。
System.gc()
System.gc(); 提示 JVM 進行 GC,但是不能確保 JVM 會這么做,JVM 可能會忽略,取決于不同的 JVM 實現。
obj.finalize()
obj.finalize() 類似于析構函數,在一個對象被真正回收之前,執(zhí)行一些清理工作。
在三種情況下 obj.finalize() 會調用:
- 對象被 GC 時自動調用,比如運行
System.gc(); - 程序退出時為每個對象自動調用一次
obj.finalize() - 顯式的調用
obj.finalize()方法
JVM 不保證 finalize() 一定被調用,也就是說 finalize() 的調用是不確定的
回收算法
分代回收算法
內存中的區(qū)域被劃分成不同的世代,對象根據其存活的時間被保存在對應世代的區(qū)域中。
3個世代:新生代(Young Generation)、老年代(Old Generation)和永久代(Perm Generation),如下圖所示:
對于不同的世代可以使用不同的垃圾回收算法。
新生代 Young Generation 進一步分為 Eden 區(qū)和 兩個 Survivor 區(qū):
- Eden 區(qū):是一塊連續(xù)的空閑內存區(qū)域,在這里進行對象內存的分配。在 Eden 區(qū)進行內存分配非常快,因為不需要進行可用內存塊的查找。
-
兩個 Survivor 區(qū):一個叫做 From,另一個叫做 To。兩個 Survivor 區(qū)中始終有一個是空閑的。
假設當前 To 區(qū)是空白的,GC 時,Eden 區(qū)和 From 區(qū)中存活的對象根據其存活的時間被復制到 To 區(qū)和 Old 區(qū)。隨后一次性回收 Eden 區(qū)和 From 區(qū),From 與 To 角色互換。
老年代(Old Generation):
任何從新生代中的 Survivor 區(qū)中幸存下來的對象會被送往老年代。老年代通常比新生代大很多。
永久代(Perm Generation):
永久代存儲類的定義及常量池,是一塊連續(xù)的堆空間,默認 64M。
永久代的 GC 與 老年代 的 GC 捆綁在一起,無論誰滿了,都會觸發(fā)永久代與老年代的 GC。
在 Java 8 中 永久代(Perm Generation) 被 Metaspace 所取代,默認128M。
對于年老代(Old Generation)和永久代(Perm Generation)區(qū)域,采用 標記-清除-壓縮(Mark-Sweep-Compact)算法:
- 標記:遍歷堆空間,找出當前還存活的對象,進行標記
- 清除:回收不可用的對象
- 壓縮:把存活對象的內存移動到整個內存區(qū)域的一端,使得另一端是一塊連續(xù)的空閑區(qū)域
Minor GC VS Major GC VS Full GC
- Minor GC 即新生代 GC:頻繁,速度快。因為 Java 對象大多都生命周期很短。
- Major GC 即老年代 GC:不頻繁,速度慢。因為老年代中的對象生命周期比較長。其速度一般會比 Minor GC 慢 10 倍以上。
- Full GC 即整個堆空間,包括新生代 GC 和老年代 GC
- 另外,如果分配了 Direct Memory,在進行 Full GC時,會順便清理掉 Direct Memory 中的廢棄對象
標記 - 清除算法 - 不支持壓縮
最基礎的收集算法,它分為“標記”和“清除”兩個階段:首先標記出所需回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象,它的標記過程其實就是前面的根搜索算法中判定垃圾對象的標記過程。
標記 - 整理算法 - 支持壓縮
該算法標記的過程與標記 - 清除算法中的標記過程一樣,但對標記后出的垃圾對象的處理情況有所不同,它不是直接對可回收對象進行清理,而是讓所有的對象都向一端移動,然后直接清理掉端邊界以外的內存。
復制式回收算法
將堆分為 A 和 B 兩個部分。遍歷 A,將 A 中的可達對象復制到 B,然后一次性回收 A。
引用:
Java 垃圾收集機制