JVM——垃圾收集器與記憶體分配

it_was發表於2020-09-20

:fast_forward:本章講述的是JVM的記憶體分配和回收
首先我們清楚,Java的記憶體區域主要有五個:程式計數器,虛擬機器棧,本地方法棧,堆和方法區。然而前三個都是執行緒私有的,隨著執行緒的建立而建立,消亡而消亡,棧中每一個棧幀分配的大小也是確定的(類載入完成後也就確定),即方法結束或者執行緒消亡的時候記憶體也就跟著回收了,不必擔心太多。
但是,堆和方法區的分配卻具有明顯的不確定性,因為你必須在程式執行期間才能知道具體分配多少記憶體,所以Java的垃圾收集器關注的就是堆和方法區!

ok,既然要做垃圾回收,那麼其物件就是那些已經死亡的物件,ok那我們如何判斷物件死亡?

:boom: 2.1引用計數法
很簡單,就是給每個物件增加一個引用計數器,如果其他地方有引用這個物件,那麼就將其計數器加一;引用失效的時候就減一;任何時候如果垃圾收集器發現某物件的引用計數器為0,就代表這個物件已經死亡,可以回收!
:+1:優點:簡單高效
:-1:缺點:計數器佔用了一些額外的記憶體,重點是需要大量的額外處理才能保證正確的工作,譬如說迴圈引用問題!

既然談到了引用我們不妨說下Java中的引用概念,主要有四種:

  • 強引用,普遍存在的,例如最經常的 “Object obj = new Object();”程式碼,即只要強引用關係還在,垃圾回收器就永遠不會回收這些被引用的物件
  • 軟引用,描述一些還有用但非必須的物件,在發生oom之前,會對這些軟引用的物件進行二次回收,如果還不行只好丟擲oom
  • 弱引用,強度更弱, 但凡讓垃圾回收器碰到必定回收!
  • 虛引用,最弱了,根本不影響物件的生存情況,唯一用來將這個物件回收時會受到一個系統通知

:boom:2.2可達性分析法
可達性分析演算法是目前主流的判斷物件死亡的演算法,此演算法主要是以一系列稱為“GC Roots”的跟物件作為起點,向下遍歷搜尋走過的路徑,這些路徑被稱為引用鏈!如果某個物件到GC Roots沒有任何引用鏈相連,那麼稱這個物件不可達!即判定其為死亡!
:exclamation:GC Roots

  • 虛擬機器棧中引用的物件(棧幀中的本地變數表)
  • 本地方法棧中JNT(即常說的native方法)引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 所有被同步鎖持有的物件

:warning:難道物件在可達性分析演算法中不可達,就說明其一定必須死亡嗎?
不一定!不可達物件還處於“緩刑”階段,即要真正判斷一個物件死亡,至少要經歷兩次標記過程!
:clock1:第一次標記:當物件不可達時進行一次標記
:clock2:篩選:從這些標記中進行篩選,檢查是否有必要執行finalize方法!如果重寫了並且沒有被呼叫過,ok,將放置在一個F-queue佇列,然後由虛擬機器的一條finalizer低優先順序執行緒去執行它,而且還不一定執行成功。否則就是不必要執行
:clock4:第二次標記:從F-queue佇列中檢查,如果物件成功自救,ok你活了下來,否則真正回收

垃圾收集器的工作區域大部分都集中在堆,對於方法區的回收也有一定的規範,只不過價效比不高,但還是要強調一下
收集的物件是:廢棄的常量和不再使用的型別!!
:boom:在大量使用反射,動態代理等位元組碼的框架,通常都需要Java虛擬機器具備型別解除安裝的能力,以保證不會對方法區造成過大的記憶體壓力!

5.1 分代收集理論
5.2 標記-清除演算法
5.3 標記-整理演算法
5.4 複製演算法

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章