好好捋一捋類載入和垃圾回收

一支彩筆發表於2018-07-21

背景

之前自己對類載入和垃圾回收的理解,總是迷迷糊糊。今天特地結合了一些文章還有《深入理解Java虛擬機器》好好梳理了一下。

廢話少說

1、類的載入

Java 的類載入過程分為三個主要步驟:載入、連結、初始化。

首先是載入階段:此階段是Java將位元組碼資料從不同的資料來源讀取到JVM中,並對映為JVM認可的資料結構(Class 物件,我們可可看到java.lang.Class) ,我們的jar檔案、class

  • 載入階段,我們可以自定義類載入器,去實現自己的類載入過程。 第二階段是連結 ,這是核心的步驟,簡單說是把原始的類定義資訊平滑地轉化入JVM執行的過程中。這裡可進一步細分為三個步驟:
  • 驗證 :這是虛擬機器安全的重要保障,JVM 需要核驗位元組資訊是符合Java虛擬機器規範的,驗證階段有可能觸發更多class的載入。
  • 準備:建立類或介面中的靜態變數,並初始化靜態變數的初始值。這裡的初始化重點在於分配所需要的記憶體空間,不會進行賦值。賦值操作在初始化階段。
  • 解析:這部分自己暫時還沒有特別的理解,等捋清了再來不上。

最後是初始化階段 , 這一步開始執行靜態欄位賦值的動作,靜態初始化塊內的邏輯,編譯器在編譯階段就已經把該執行的程式碼邏輯整理好了,這裡需要注意的是:父類的初始化邏輯優先於子類的邏輯。

一直走到這,是類載入完畢,也是生成了我們對應的Class。但是請留意,這裡還沒有涉及到類的例項化,也就是說此時還沒有開始new操作。

當執行new的時候,而過此類如果已經經歷過載入,那麼就會執行對應的例項化,比如分配記憶體,執行程式碼塊,構造方法之類的(如果有父類要先對應執行這些內容)。最後將記憶體地址指向引用。


2、垃圾回收

其實這是一個老生常談的話題。為啥還要再提,就是我和MDove聊天的過程中發現我們的理解都不夠成體系。所以我們參考了很多文章重新把這相關的內容,捋一捋。

什麼樣的物件可以被回收?

我相信大家應該都明確“可達性分析”這個概念吧,就是一種用於標記物件是否能被回收的做法。我們先簡單看一下幾種能被回收的情況。 我們先假設有一個被認定為GC Root的物件a=new A(),它內部引用了b =new B(),b內部又引用了c=new C()。

例子1

(1)c=new C();
(2)c=new C();//重新new了一個新的C物件。

複製程式碼

那麼我們(1)過程中被new的物件就可以被回收(這裡需要注意,當這個物件被標記倆次可回收時,才會回收)

例子2

(1)c=new C();
(2)c=null;
複製程式碼

此時我們(1)過程中new的物件也可以被回收

例子3

此外所有方法內有區域性變數引用的,新new出來物件,在方法結束後都會被回收。


3、常見垃圾回收演算法

這裡特別以英文命名,因為翻譯大家並沒有一個統一的叫法。所以就一英文為主吧。

Mark-Sweep(標記-清除演算法): 思想簡單粗暴:標記階段標記出所有需要回收的物件,清除階段回收被標記的物件所佔用的空間。但是問題也很直接,會造成記憶體碎片,到來的後果就是明明空間夠,但是申請不出來記憶體(OOM),就是因為碎片的原因,沒辦法申請對應大小的連續記憶體了。

Copying(複製演算法): 為了應對Mark-Sweep的缺點,應運而生了Copying,它的思想按記憶體容量大小,將記憶體劃分為兩塊相同的記憶體。每次只使用其中一塊,當這一塊記憶體滿了之後,將還活著的物件複製到另一塊記憶體上,並且把可回收的記憶體回收掉,以此來保證當前使用的記憶體是連續的。但是缺點也很直接,那就是把可以使用的記憶體砍了一半。而且如果存活物件較多,複製起來速度會比較的慢。

Mark-Compact(標記-整理演算法): 此演算法是結合了上述倆種演算法的綜合。標記階段和Mark-Sweep類似,標記後並非直接清除,還是將存活物件一項記憶體的一端,清掉其他內容,那麼這樣就可以就解決Mark-Sweep中的碎片問題,而且沒有Copying中的記憶體分塊。但是頻繁的移動,對速度要求會比較的高。

Generational Collection(分代):

以下只是大概說一個籠統的思路,因為對應不同的分代情況,不同的垃圾回收器會有自己的演算法實現。

綜合以上的想法,出現了比較被廣泛接受的演算法思路。也就是我們常提的按物件的生老病死的比例分塊(Copying的思想),認可度比較高的做法:將新生代劃分為一塊較大的Eden空間和兩個較小的Survivor空間(From Survivor, To Survivor),分別對應8:1:1,老年代另算。正常使用時會使用新生代的Eden和From Survivor空間,如果new物件時,空間不足,觸發一次GC。GC後,Eden和From Survivor區的存活物件會被挪到To Survivor,然後將Eden和From Survivor進行清理。如果To Survivor無法足夠儲存某個物件,則將這個物件儲存到老生代。之後就可以正常再使用Eden和To Survivor了。當物件在Survivor區經歷一次GC後,其年齡就會+1。年齡達到一定程度(可以配置)後會被移到老生代中。


4、具體的垃圾回收器

因為目前被接受的垃圾回收的思路為分代的思想,因此為了能各高效的利用資源,正真被實現出來的垃圾回收器都會根據不同的代,做不同的處理。比如:

1. Serial/Serial Old

最古老的收集器,是一個單執行緒收集器,用它進行垃圾回收時,必須暫停所有使用者執行緒。Serial是針對新生代的收集器,採用Copying演算法;而Serial Old是針對老生代的收集器,採用Mark-Compact演算法。優點是簡單高效,缺點是需要暫停使用者執行緒。

2. ParNew

Seral/Serial Old的多執行緒版本,使用多個執行緒進行垃圾收集。

3. Parallel Scavenge

新生代的並行收集器,回收期間不需要暫停其他執行緒,採用Copying演算法。該收集器與前兩個收集器不同,主要為了達到一個可控的吞吐量。

4. Parallel Old

Parallel Scavenge的老生代版本,採用Mark-Compact演算法和多執行緒。

5. CMS

Current Mark Sweep收集器是一種以最小回收時間停頓為目標的併發回收器,因而採用Mark-Sweep演算法。

6. G1

G1(Garbage First)收集器技術的前沿成果,是面向服務端的收集器,能充分利用CPU和多核環境。是一款並行與併發收集器,它能夠建立可預測的停頓時間模型。

總結一下:現在的回收思想是以分代為基礎,對於不同的代,對應實現最佳的演算法(不同業務場景會對應不同的最佳方案)。

以上內容算是我個人的一個學習總結,如果有不當之處,歡迎評論區之處。共同學習,共同進步。


尾聲

這是一個主推面試踩坑的公眾號!

不感興趣的就直接無視掉吧

我們是一個應屆生學習小組,這些分享我們會堅持下去,一定會。這條路大家都走的太辛苦,一起互相鼓勵,一起並肩同行!

因為身邊的同學從事網際網路相關職業的比較多,並且大家閒時聊天時總會吐槽找工作有很多坑,所以打算把身邊同學找工作的經驗,統統收集起來。提供給想從事這方面同學,希望圈內好友可以共同進步,共同少踩坑。

個人公眾號

(打個廣告)我們基友團其他朋友的文章:

Web基友 Android基友

相關文章