1. 程式人生 > 其它 >淺談java虛擬機器GC垃圾回收機制

淺談java虛擬機器GC垃圾回收機制

一個物件何時會成為垃圾?——如果一個物件我們再也訪問不到了,那麼該物件就變成垃圾了。由於對於物件的訪問,總是間接的,總是需要通過應用變數才能訪問的,那就意味著,當沒有任何一個(有效的)引用變數指向該物件的時候,該物件變成垃圾。 目錄

JVM Memory

Java虛擬機器在Java程式執行過程中會把它管理的記憶體,劃分為若干個不同的資料區域。這些區域各自的用途,以及建立和銷燬的時間,有的區域隨虛擬機器的啟動而存在,有些區域依賴使用者執行緒的啟動和結束而建立和銷燬。

執行時JVM資料區
  1. 程式計數器

    程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行的位元組碼的行號指示器。(每個執行緒都有自己的程式計數器,執行緒隔離)。

  2. JAVA虛擬機器棧

    它描述的是Java 方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame )用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。執行緒私有(執行緒隔離)。

    棧空間隨著執行緒的開始而開始,執行緒的終結而終結。

    執行緒之間不會有區域性變數的資料安全問題。

  1. 本地方法棧(執行緒私有)

    本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java 方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native 方法服務。

  2. JAVA堆

    此記憶體區域的唯一目的就是存放物件例項,一個JVM例項只存在一個堆,堆記憶體的大小是可以調節的。堆記憶體是執行緒共享的。

  3. 方法區

    方法區(Method Area)與Java 堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數等資料。

GC

記憶體管理方式

記憶體管理管理的方式通常有兩種

  • 顯式的記憶體管理(C/C++)

    記憶體管理(記憶體的申請和釋放)是程式開發者的職責。
    常見問題:

    • 記憶體洩漏:記憶體空間已經申請,使用完畢後未主動釋放。

    • 野指標:使用了一個指標,但是該指標指向的記憶體空間已經被free。

  • 隱式的記憶體管理(Java/C#)

    記憶體的管理是由垃圾回收器自動管理的(記憶體的回收)

    • 優點:增加了程式的可靠性(和野指標),減小了memory leak。
    • 缺點:無法控制GC的時間,耗費系統性能。

GC——Garbage Collection

在JVM中,GC的功能是由垃圾回收器來完成。

研究GC,就必須要回答下面3個問題:

  • 如何確定“垃圾”
  • 如何回收垃圾
  • 何時觸發GC
如何確定垃圾

一個物件何時會成為垃圾?——如果一個物件我們再也訪問不到了,那麼該物件就變成垃圾了。由於對於物件的訪問,總是間接的,總是需要通過應用變數才能訪問的,那就意味著,當沒有任何一個(有效的)引用變數指向該物件的時候,該物件變成垃圾。

兩種方法。

引用計數法

核心依據就看該物件是否還有引用變數引用它。

  • 給物件新增一個引用計數器

    引用計數器:記錄當前jvm中有多少個引用變數指向該物件(是一個整數)。

  • 每當一個地方引用它時,計數器加1

  • 每當引用失效時,計數器減少1

  • 當計數器的數值為0時,也就是物件無法被引用時,表明物件不可在使用

缺點:無法解決迴圈引用的問題。商用JVM一般不使用引用計數法。

根搜尋演算法

核心依據就看該物件能否被訪問到。

  • 這個演算法的基本思想是將一系列稱為“GC Roots”的物件作為起始點。

    GC Roots是一個物件集合,包含:

    1. 虛擬機器棧中引用的物件——通過棧引用一定可以訪問到的物件。
    2. 方法區中的靜態屬性引用的物件。類名.靜態變數。靜態引用變數指向的物件,我們一定可以訪問到。

    簡單來講GC Roots就是一定可以訪問到的物件的集合。

  • 從這些節點(沿著引用鏈)開始向下搜尋。

  • 搜尋所走的路徑稱為引用鏈

  • 當一個物件到所有的GC root之間沒有任何引用鏈相連,此時,就認為該物件變成了垃圾

如何回收垃圾
標記清除演算法(Mark Sweep)

優點:實現簡單。

缺點:容易產生記憶體碎片。

標記複製演算法(Copy)
  1. 堆記憶體被平均分成兩塊。
  2. 每次做記憶體分配,只使用其中一塊,另一塊保留記憶體不用來分配。該保留記憶體在垃圾回收的時候使用。
  3. 記憶體回收的時候,複製演算法會將用來分配的那一半記憶體的存活物件都一次複製到保留記憶體中,依次挨個存放。(在一次回收過後,沒有記憶體碎片)。然後剛剛用來分配的那一塊記憶體,一次整個回收掉。
  4. 於是在下一次記憶體回收之前,剛剛被回收掉的原本用來分配記憶體的儲存區域就變成了新的保留記憶體,而剛剛的保留記憶體就用來分配記憶體。

優點:

  1. 一次記憶體回收過後,沒有記憶體碎片;
  2. 當一次堆記憶體回收過程當中,如果存活物件很少,即需要複製的物件很少,而且又因為大片的連續儲存空間的回收效率極高。

缺點:

  1. 記憶體利用率低,每次只用一般記憶體來分配;
  2. 如果每次存活的物件很多,複製就會比較多,效率就不是很高了。
標記整理演算法(Mark Compact)

拿時間換空間,空間利用率百分百,但是需要花時間整理存活物件並且整理移動位置。

分代收集演算法(Generational Collection 商用)
  • 這種演算法並沒有什麼新的思想,只是根據物件存活週期的不同將記憶體劃分為幾塊

  • 一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最合適的收集演算法。

  • 在新生代中,每次垃圾收集時都發現有大批物件死去,只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。

    98%的物件,朝生夕死。

  • 而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-整理”演算法進行回收。

    物件的年齡:一個物件,在新生代當中每經歷一次垃圾回收都存活下來,該物件的年齡就增長一歲。如果一個物件在新生代中,連續經歷15此垃圾回收仍然存活,就進入老年代。

GC相關概念
  • Shallow size

    就是物件本身佔用的記憶體大小,也就是物件頭加成員變數佔用記憶體大小的總和。

  • Retained size
    是該物件自己的shallow size 加上僅可以從該物件訪問(直接或者間接訪問)的物件的shallow size之和。

Retained size是該物件被GC之後所能回收的記憶體的總和。

GC觸發的時機
  • 申請heap space失敗後會觸發GC回收
  • 系統進入idle後一段時間會進行回收(系統空閒)
  • 主動呼叫GC進行回收 System.gc()

記憶體相關問題

Out of Memory case

Heap OOM 堆溢位

Stack Overflow 棧溢位

記憶體洩漏(Memory Leak)

是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

記憶體洩露與記憶體溢位聯絡

記憶體洩露可能導致記憶體溢位,但不是必然導致記憶體溢位。