深入理解Java虛擬機閱讀心得(二)
垃圾收集
程序計數器、虛擬機棧、本地方法棧三個區域隨線程而生,隨線程而滅;這幾個區域的內存分配和回收都具備穩定性,不需要過多的考慮回收的問題。而Java堆和方法區則不一樣。
Java堆中存儲了幾乎所有的對象實例,垃圾收集器進行對堆的回收之前,需要判斷這些對象是否還存活
一。判斷對象是否存活
判斷對象是否還活着,主要有兩種方法
1.引用計數法
給對象添加一個引用計數器,每當一個地方引用時,計數器值加1;
每當一個引用失效時,計數器減1;
任何計數器爲0的對象爲不可能被使用的對象。
優點:實現簡單,判定效率高
缺點:很難解決對象之間的相互循環引用的問題
2.可達性分析法
主流的實現中,都是通過可達性分析法來判定對象是否存活。
該算法基本思想:通過一系列稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索的路徑稱爲引用鏈;
當一個對象到GC Roots沒有任何引用鏈相連時(即GC Roots無法到達該對象),稱爲不可達對象,該對象不可用
此時,可以判定該對象是可回收的對象。
Java中可以作爲GC Roots的對象包括以下四種:
(1)虛擬機棧中引用的對象
(2)方法區中類靜態屬性引用的對象
(3)方法區中常量引用的對象
(4)本地方法棧中JNI(一般說的是Native方法)引用的對象
3.方法區的回收
由於方法區中主要存放的是永久代對象,因此,方法區中進行垃圾收集的性價比一般較低。
永久代的垃圾回收主要分爲兩類:1.廢棄常量 2.無用的類
類的卸載條件比較苛刻,需要同時滿足以下三個條件:
(1)該類的所有實例已經被回收,即Java堆中不存在該類的任何實例
(2)加載該類的ClassLoader(即類加載器)已被回收
(3)該類對應的java.lang.Class對象沒有在任何地方唄引用,無法在任何地方通過反射訪問該類的方法
在大量使用動態代理、反射的場景,需要虛擬機具備卸載類的功能,以保證永久代不會造成內存溢出
4.finalize()
類似 C++ 的析構函數,用於關閉外部資源。
但 try-finally 等方式可以做得更好,並且該方法運行代價很高,不確定性大,無法保證各個對象的調用順序,因此最好不要使用。
當一個對象可被回收時,且被判定爲有必要執行該對象的 finalize() 方法,那麼可能讓該對象自救(只需要重新與引用鏈上的任何一個對象建立關係即可)。
自救只能進行一次,如果回收的對象之前調用了 finalize() 方法自救,後面回收時不會再調用該方法。
二。強引用與軟引用
Java對引用的概念分爲四種,從強到弱依次爲:強引用,軟引用,弱引用,虛引用
1.強引用
代碼中普遍存在的類似 Object obj = new Object()這種,只要強引用在,該對象永遠不會被回收
2.軟引用
用來描述一些還有用但非必須的對象;提供了SoftReference類來實現軟引用
在系統發生內存溢出異常之前,會把這些對象列入回收範圍之中,進行第二次回收
3.弱引用
也是用來描述非必須對象,但是比軟引用更弱;提供了WeakReference類來實現
弱引用關聯的對象,只能生存到下一次垃圾收集發生之前。
當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。
4.虛引用
最弱的引用關係;提供PhantomReference類實現
一個對象是否有虛引用的存在完全不會對其生存時間產生影響,同時也無法通過虛引用來取得一個對象的實例。
設置虛引用的唯一目的是,通過虛引用在這個對象被回收時收到一個系統通知