JVM垃圾回收機制的運行原理

對於垃圾JVM的垃圾回收機制這裏我們稱爲GC,衆所周知,java語言不需要像c++那樣需要自己申請內存,自己釋放內存,這些都是JVM幫我們做好了的,但是對於一名java程序員,想要更近自己的水平更上一層樓,就要去了解GC的工作原理,根據原理才能寫出更好的更優的程序,這裏我們先初步講解一下GC的工作原理


首先我們在講解之前我們需要了解一下JVM內存運行時數據區的三個重要的地方

  • 堆(heap) : 他是最大的一塊區域,用於存放對象實例和數組,是全局共享的.
  • 棧(stack) : 全稱爲虛擬機棧,主要存儲基本數據類型,以及對象的引用,私有線程
  • 方法區(Method Area) : 在class被加載後的一些信息 如常量,靜態常量這些被放在這裏,在Hotspot裏面我們將它稱之爲永生代

關於具體結構可以用下面這張圖來對內存運行時數據區有一個大致的概括 
這裏寫圖片描述

堆(heap):,前面已經說了他是最大的,也是最重要的一塊區域,這裏也稱爲邏輯堆,主要用來存放對象實例與數組,對於所有的線程來說他是共享的,對於Heap堆區是動態分配內存的,所以空間大小和生命週期都不是明確的,而GC的主要作用就是自動釋放邏輯堆裏實例對象所佔的內存,而在邏輯堆中還分爲新生代與老年代,用來區分對象的存活時間,在新生代中還被細緻的分爲 Eden SurvivorFrom以及SurvivorTo這三部分.


方法區(Method Area):方法區主要存儲(類加載器)ClassLoader加載的類信息,在這裏我們可以理解爲已經編譯好的代碼儲存區,所以存儲包括類的元數據,常量池,字段,靜態變量與方法內的局部變量以及編譯好的字節碼,等等

棧(stack):在每一個對象被創建的時候,在堆棧區都有一個對他的引用,在這裏我們可以這樣理解。

Object obj = new Object();
  • 1

上面的代碼左邊的Object obj 等於在堆棧區申請了一個內存,這裏也就是對類的引用了,而 new Object()則是生成了一個實例,=則是 將對象的內容則可通過obj進行訪問,在Java裏都是通過引用來操縱對象的。

pc寄存器(PC Regesiter): 在多線程中,系統需要給每一個線程 分配一個進程編號,這個時候纔會需要到寄存器。


好了 基本的都介紹完了,現在來進入正題 :

我們知道對象的實例是存在於邏輯堆中,而GC在邏輯堆是怎樣運行的呢,下面我們看下邏輯堆的具體結構 
這裏寫圖片描述 
就像上面的圖可以看出 邏輯堆分爲 年輕代與年老代,對於非堆內存這裏先不講解,而年輕代則被分爲 eden survivor1 survivor2 ,對於一個新被實例化的對象都是存在於年輕代中的eden區,至於爲什麼,下面會講到,eden中文名爲伊甸園,按照GC的運行機制,會回收掉已經死掉的對象,而對象一般都是在年輕代就會死去,所以年輕代比老年代需要更頻繁的GC清理,下面針對年輕代與老年代的回收機制有不同的講解

年輕代 :

在年輕代中jvm使用的是Mark-copy算法,就像算法名字說的那樣有兩個步驟,第一是標記(Mark) 第二是copy(複製),Mark主要用於標記出還活着的實例,然後清除掉沒有被標記的實例,釋放內存,然後Copy部分則是將還活着的實例根據年齡拷貝到不同的年齡代,而jvm又是根據什麼來區分年齡代,和實例存活與不存活的呢? 下面將對這個過程進行講解


對於標記 與區分年齡代的技術 我們一般都是用到的都是引用計數器,在每一個對象中都含有引用計數器,都有引用指向對象的時候 引用計數器就會加1,不在被引用 計數器 減 1,對與垃圾回收的策略則是標記所有活着的實例,將沒有被標記的實例全部回收 釋放內存,


對於靜態,我們都知道靜態方法與靜態變量是不會產生實例的,直接通過類的引用,使用 ClassLoader進行加載的類數據如前面所說是不存在邏輯堆裏面的,直接存在於永生代裏面也就是 方法區裏面,這個類一旦被清除掉裏面所有的靜態變量都會被清除

當我們在 Object obj 的時候 向邏輯堆中的 Eden區域 申請內存,當Eden區域的內存不足的時候,這個時候會觸發GC這個時候稱gc爲小型垃圾回收,每個實例都有一個獨有的年齡,每個引用被經歷過一次GC後就會年齡加一,同時就會將沒有被清理掉的對象全都copy到上圖的survivor1區域,如圖1所示: 
這裏寫圖片描述 
當第二次GC執行的時候就會使用Mark算法找到存活的對象,然後將他們的年齡加1,並且將他們拷貝到survivor2區域,然後執行GC,這樣就可以實現survivor1 與 survivor2 兩個一樣大的區域進行交替使用,當對象的年齡足夠大的時候,對象就會被移動到老年代,這裏移動到老年代的標準由JVM的參數所決定

年老代 :

當GC被觸發的時候 eden的對象會轉到 survivor1 然後再次就會轉到 survivor2 ,當survivor1的對象太大了 survivor2的區域無法容納得部分就會轉到Tenured的區域,當Tenured的區域也容不下的時候就會自動移動到年老代,在移動年老代的時候會先觸發年老代上面的GC然後在將Tenured容納不下的對象放入年老代,對於年老代的GC算法與年輕代的Mrak-copy算法有很大不同

年老代的GC算法在jdk 1.7 中分爲五種,

Serial GC 
Parallel GC 
Parallel Old GC(Parallel Compacting GC) 
Concurrent Mark & Sweep GC (or “CMS”) 
Garbage First (G1) GC

在這裏我只講解兩種Parallel Scavenge 與 Concurrent Mark sweeps 對與這兩種接下來會進行簡單的講解

Parallel Scavenge :

在這裏我們簡稱他爲PS算法,PS算法執行的是 Mark-compact算法的過程 ,並且是用多線程進行執行這樣提高了執行效率,這裏的Mrak還是與之前的年輕代的Mark原理是一樣的,但是Compat算法則是將年老代的對象進行碎片化的整理,並且年老代是沒有像年輕代的那樣有survivor1 與 survivor2來將殘留的對象全部copy過去,考慮到年老代的對象比較多,所以就需要進行碎片化整理如下圖: 
這裏寫圖片描述

Concurrent Mark sweeps:

我們簡稱他爲 CMS 算法,對與cms算法,我們先需要了解一個概念 Stop the world,對於Stop the world,不管選擇哪種GC算法,stop-the-world都是不可避免的。Stop-the-world意味着從應用中停下來並進入到GC執行過程中去。一旦Stop-the-world發生,除了GC所需的線程外,其他線程都將停止工作,中斷了的線程直到GC任務結束才繼續它們的任務。GC調優通常就是爲了改善stop-the-world的時間。在CMS GC開始時的初始標記(initial mark)比較簡單,只有靠近類加載器的存活對象會被標記,因此停頓時間(stop-the-world)比較短暫。在併發標記(concurrent mark)階段,由剛被確認和標記過的存活對象所關聯的對象將被會跟蹤和檢測存活狀態。此步驟的不同之處在於有多個線程並行處理此過程。在重標記(remark)階段,由併發標記所關聯的新增或中止的對象瘵被會檢測。在最後的併發清理(concurrent sweep)階段,垃圾回收過程被真正執行。在垃圾回收執行過程中,其他線程依然在執行。得益於CMS GC的執行方式,在GC期間系統中斷時間非常短暫。CMS GC也被稱爲低延遲GC,

關於GC的原理就講到這裏了,這篇博客只講解了最基本的GC 接下來要學習的還有更多,加油吧

文章標籤: jvmjavaheap
個人分類: Java虛擬機
想對作者說點什麼? 我來說一句
  • caipeichao2
    whitejava22018-03-20 07:54:23#2樓
    我覺得有兩處錯誤。 ------------- 原文 “我們一般都是用到的都是引用計數器,在每一個對象中都含有引用計數器,都有引用指向對象的時候 引用計數器就會加1” Java HotSpot中並不是使用引用計數法判斷哪些對象要回收,而是通過遍歷判斷哪些對象可達,剩下不可達的就是可回收的對象。 ---------- 原文:“survivor2的區域無法容納得部分就會轉到Tenured的區域,當Tenured的區域也容不下的時候就會自動移動到年老代” Tenured就是老年代啊,並沒有Tenured移動到老年代的說法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章