首先給大家講一個梗,為什麼Java程式設計師越來越多,而C++程式設計師越來越少呢?—— 那還不是因為不用管理記憶體啊
在C++中,自己new的物件,在不用的時候需要手動釋放。而在Java裡,你可以釋放你的雙手,將這項工作交給JVM自己管理。在JVM裡面,這種機制叫做垃圾回收(Garbage Collection)機制。
ps:本文不涉及垃圾收集器,主要是講垃圾回收的思路。
Java記憶體管理
Java的變數一共儲存在三個地方、方法區、堆、棧
方法區
主要存放常量,靜態常量、常量池,在程式編譯的時候,這塊記憶體就已經被分配出來了,伴隨程式的一生。
堆
是JVM管理的記憶體最大的一塊,存放所有的例項物件,也是垃圾收集器管理的主要區域。也稱為“GC堆”
棧
當方法被執行的時候,方法內部的區域性變數就會被儲存在棧裡,當方法執行完畢的時候,剛剛儲存的區域性變數會被自動釋放。
回收流程
- 首先會判斷物件是否存活
- 利用回收演算法對物件進行回收
如何判斷物件是否存活?
前面說到垃圾收集器管理的是堆,在回收之前,通常需要判斷這個物件是否是存活的。
引用計數法
遍歷物件,當這個物件被其他物件引用的時候計數器就加1,當引用失效的時候,計數器減1。當物件的計數器為0時,則表示該物件沒有被引用。這種方法效率很高,但是無法解決互相引用的問題。因此主流JVM都未採用這種方法。
可達性分析
這一演算法的思想是從GC Roots作為起始點,從這些節點往下搜尋,搜尋的路徑叫做引用鏈,當一個物件沒有任何引用鏈可以到GC ROOT的時候,則證明該物件是不可用的。一般想要宣告一個物件死亡,至少要經歷兩次標記,即標記-篩選-標記。
垃圾回收演算法
標記-清除
過程
- 標記出所有需要回收的物件
- 清除所有被標記的物件
不足:
- 清除和標記兩個效率都不高。
- 會產生大量的記憶體碎片,會導致在分配較大物件的時候,如果找不到足夠的空間會再次清除。
複製演算法
過程:
將可用記憶體分為兩塊,每次只使用其中的一塊。一塊記憶體使用完的時候,將存活下來的物件複製到另一塊中,再清除這一塊記憶體。這樣就沒什麼記憶體碎片可言了。
優點:
效率高,解決了記憶體碎片的問題。
缺點:只能使用一半的空間。
不過現代虛擬機器幾乎都不是1:1分配空間,因為大部分都物件的生命週期都不長。例如HotSpot虛擬機器,預設的Eden和Survivor的大小比例是8:1。標記 — 整理演算法
複製-收集演算法,在物件存活率高的情況下,要進行多次複製,效率比較低。所以說為此提出了“標記-整理”演算法。
過程:
標記過程同上,然後讓所有存活的物件移動到另一端,然後清理掉邊界外的記憶體。
分代收集演算法
分代收集演算法比較常見,一般把堆分為新生代和老年代,這樣可以根據各個年代的特點採用適當的演算法。
- 新生代:新生代會有大批物件死去,少量物件存活。適合複製演算法。
- 老年代:老年代中物件存活率較高,沒有額外 空間來分配擔保。所以說適合採用標記-清除 或 標記-整理演算法。
ps:空間分配擔保是用來檢測老年代最大可用的連續空間是否大於新生代所有活著的物件的總空間。
引用型別
在JDK1.2之後,Java對引用的概念進行了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)四種,這四種引用強度依次逐漸減弱。
強引用(Strong reference)
就是指在程式程式碼之中普遍存在的,例如new出來的物件,只要強引用還在,該物件就不會被回收。
軟引用(Soft Reference)
用來描述一些還有用但並非必須的物件。在記憶體即將溢位之前,垃圾收集器會將這些物件進行回收。
弱引用(Weak Reference)
使用者描述非必須物件的。無論當前記憶體是否足夠,隨時都有可能被回收。
虛引用(Phantom Reference)
虛引用不會影響該物件都生命週期,只會在該物件被回收的適合獲得一個系統通知。