JVM內存結構與垃圾回收機制

本文主要講JVM內存結構與垃圾回收機制,首先講JVM內存的結構,這是爲後邊講JVM垃圾回收機制打下基礎。

JVM內存結構

在JVM運行的時候所分配內存區的結構,分爲程序計數器、虛擬機棧、本地方法棧、堆和方法區五部分,見下圖:
在這裏插入圖片描述

程序計數器

程序計數器是一塊較小的內存空間,主要用來存儲當前線程所執行的字節碼的行號指示器。
它是線程私有的,隨線程生隨線程死。
如果線程正在執行的是一個java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是調用C++/C等其他語言的方法,這個計數器則爲空。

虛擬機棧

每個線程會有一個私有的棧。每個線程中方法的調用又會在本棧中創建一個棧幀。在方法棧中會存放編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用。

本地方法棧

每個線程會有一個私有的本地方法棧。本地方法棧與虛擬機棧所發揮的作用是非常相似的。區別是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用的C++/C等其他語言的方法服務。

Java堆是Java虛擬機所管理的內存中中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的地就是存放對象實例,所有的對象實例都在這裏分配內存。Java堆是垃圾收集器管理的主要區域。

方法區

方法區與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。在Java8中永生代徹底消失了。

例子

下圖是一個很簡單的函數調用,輸出結果是20 10。咱們可以分析其在JVM內存結構
在這裏插入圖片描述
首先從func1函數說起,func1運行中首先拿到一個參數a,這個a是一個值傳遞,當前函數就會有爲其新開一個內部存儲,在棧中有了一個a變量,標記爲a爲10。然後執行到變量b初始化爲10,這時棧中就會有一個b變量,標記爲b爲10。輸出a+b以後,設置a等於11,就是把之前棧中a的值變更爲11。執行完a等於11後整個函數就執行完啦,這個函數棧中所有變量就會被銷燬,銷燬的順序按照棧先入後出的順序先銷燬b再銷燬a。
然後說下main函數,main函數和func1類似,也有一個自己的函數棧,初始化a爲10後調用func1方法,這時候輸出a還是10。func1修改a=11對其並沒有影響,因爲這時候func1的調用已經完成,內部變量已經回收啦。

垃圾回收機制

隨着程序的運行,內存中的實例對象、變量等佔據的內存越來越多,如果不及時進行回收,會降低程序運行效率,甚至引發系統異常。
在上面介紹的五個內存區域中,有3個是不需要進行垃圾回收的:本地方法棧、程序計數器、虛擬機棧。因爲他們的生命週期是和線程同步的,隨着線程的銷燬,他們佔用的內存會自動釋放。所以,只有方法區和堆區需要進行垃圾回收,回收的對象就是那些不存在任何引用的對象。下面我們講的是針對堆的垃圾回收,因爲方法區的垃圾回收較少。
說到垃圾回收,有兩個問題非常關鍵:
1. 那些對象需要回收?
2. 找到需要回收的對象以後具體怎麼清理?
下面咱們一個個解決這些問題。

那些對象需要回收?

關於對象是否存活的算法有引用計數算法和可達性分析算法,下面我們簡單介紹下。

引用計數算法

引用計數算法是在JVM中被摒棄的一種對象存活判定算法,不過它也有一些知名的應用場景(如Python、FlashPlayer),因此在這裏也簡單介紹一下。

用引用計數器判斷對象是否存活的過程是這樣的:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器加1;當引用失效時,計數器減1;任何時刻計數器爲0的對象就是不可能再被使用的。

引用計數算法的實現簡單,判定效率也很高,大部分情況下是一個不錯的算法。它沒有被JVM採用的原因是它很難解決對象之間循環引用的問題。

可達性分析算法

Java、C#等使用可達性分析算法來判斷對象是否存活的。可達性分析算法的基本思想是通過一系列的稱爲“GC Roots"的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何飲用鏈相連時,則證明此對象是不可用的。如下圖,對象object5、object6、object7雖然互相有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定是可回收的對象。
在這裏插入圖片描述
在Java語言中,可作爲GC Roots的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中引用的對象

垃圾收集算法

使用可達性分析算法可以找到需要清理的對象,找到這些需要清理的對象以後我們應該怎麼清理那?本節將介紹幾種垃圾收集算法的思想及其發展過程,JAVA垃圾回收的具體實現將在稍後介紹。

標記-清除算法

標記-清除算法是最基礎的垃圾收集算法,後續的收集算法都是基於它的思路並對其不足進行改進而得到的。顧名思義,算法分成“標記”、“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象.
不足有兩點:

  • 效率問題 標記和清除兩個過程的效率都不高
  • 空間問題 標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不觸發另一次垃圾收集動作。
    標記-清除算法的執行過程如下圖所示:
    在這裏插入圖片描述

複製算法

爲了解決標記-清除算法的效率問題,一種稱爲“複製”的收集算法出現了,思想爲:它將可用內存按容量分成大小相等的兩塊,每次只使用其中的一塊。當這一塊內存用完,就將還存活着的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。

這樣做使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲原來的一半,代價可能過高了。複製算法的執行過程如下圖所示:
在這裏插入圖片描述

標記-整理算法

複製算法在對象存活率較高時要進行較多的複製操作,效率將會變低。未來解決效率問題,標記-整理算法被提出來。主要思想爲:此算法的標記過程與標記-清除算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。具體示意圖如下所示:
在這裏插入圖片描述

實際GC過程

Java的實際GC採用了“分代收集”算法,一般把Java堆分爲新生代(Young)和老年代(Old)。如下圖:
在這裏插入圖片描述
新生代:是所有新對象產生的地方,使用複製算法進行清理。年輕代被分爲3個部分——Enden區和兩個Survivor區(From和to)。當Eden區被對象填滿時,就會執行Young GC。並把所有存活下來的對象轉移到其中一個survivor區。Young GC同樣會檢查存活下來的對象,並把它們轉移到另一個survivor區。這樣在一段時間內,總會有一個空的survivor區。經過多次GC週期後,仍然存活下來的對象會被轉移到年老代內存空間。年輕代升級爲年老代的閾值默認爲15,這裏的15是指在新生代經歷過15次Young GC。需要注意,Survivor的兩個區是對稱的,沒先後關係。
老年代:使用標記清理或者標記整理算法。 在年輕代中經歷了N次回收後仍然沒有被清除的對象,就會被放到年老代中,可以說他們都是久經沙場而不亡的一代,都是生命週期較長的對象。對於年老代和永久代,就不能再採用像年輕代中那樣搬移騰挪的回收算法,因爲那些對於這些回收戰場上的老兵來說是小兒科。通常會在老年代內存被佔滿時將會觸發Full GC,回收整個堆內存。

爲什麼分區

在新生代中,每次垃圾回收都發現有大批對象死去,只有少量存活,選擇複製算法只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或者“標記-整理”算法來進行回收。

GC具體執行流程

來源Java Garbage Collection Basics

  1. 首先新對象都會在新生代的Eden區,兩個survivor都是空的。
    在這裏插入圖片描述
  2. 當Eden區滿了以後,會觸發Young GC進行垃圾回收。
    在這裏插入圖片描述
  3. 無需清理的對象被移動到第一個survivor空間。清除Eden區時,將刪除需要清理的對象。
    在這裏插入圖片描述
  4. 在下一個Young GC中,Eden區也會發生同樣的事情。刪除未引用的對象,並將引用的對象移動到survivor空間。但是,在這種情況下,它們被移動到第二個survivor空間(S1)。此外,來自第一個survivor空間(S0)上的最後一個Young GC後存活對象的年齡增加1並且移動到S1。所有被引用對象被移動到S1,S0和eden都會被清除。這時候的survivor空間存活對象的年齡不同。
    在這裏插入圖片描述
  5. 在下一次 Young GC中,重複相同的過程。然而這次survivor空間切換。引用的對象被移動到S0。倖存的對象年齡加1。Eden和S1被清除。
    在這裏插入圖片描述
  6. 在多次Young GC之後,倖存的對象達到一定的年齡閾值(在該示例中爲8)時,它們從新生代晉升到老年代。

在這裏插入圖片描述

  1. 隨着Young gc的不斷髮生,新生代的倖存對象會被逐步的移動到老年代。

在這裏插入圖片描述
8.這是整個新生代的整個過程。最終,將對老年代進行Full GC,清理和壓縮該空間。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章