JVM中記憶體和GC的介紹

努力的碼農發表於2018-12-14

最近聽聞很多裁員潮,寒冬潮的訊息,所以才會有下面這些總結,給道友準備,也是為自己!
希望自己用不到.................

記憶體分佈

jvm將記憶體分為了堆,方法區,程式計數器,棧(虛擬機器棧),本地方法棧5個區域,有些是執行緒共有的有的是執行緒共享的。下面就簡單就這幾個區域做簡單說明。

程式計數器(PC暫存器)

作用:儲存當前執行緒正在執行的java方法的JVM指令地址,如果執行的是Native方法,則為undefined。
每個執行緒都有自己的PC暫存器。

Java棧(虛擬機器棧)

作用:存放方法呼叫時產生的幀,資料結構是先進後出的棧。
每一次的呼叫都會產出一個新的幀,方法的執行伴隨著入棧和出棧的操作;該幀儲存java方法的區域性變數,運算元棧,常量池指標等。同時內部包含了一個區域性變數表。執行緒私有

本地棧

類似Java棧,只不過是在呼叫native方法時才會使用到

作用:存放Java物件例項,幾乎所有建立的Java物件例項都會被直接分配在堆上,被所有執行緒共享,可以在啟動JVM時通過Xmx,Xms等來指定該區域的大小,也是GC工作的主要區域。

方法區

作用:存放一些後設資料,如類結構資訊,常量,靜態變數,常量池也存放於此。在jdk1.7及之前的版本中,將方法區稱為永久代,而1.8之後去除了永久代,增加了後設資料區。方法區也是執行緒共享的

總結:執行緒共享的有方法區和堆,執行緒私有的是程式計數器,Java棧和本地方法棧。

GC

垃圾收集的目的是為了回收那些佔用了記憶體但是又不會再被使用的物件。

基本收集演算法

  • 引用計數 為物件新增一個引用計數,用於記錄物件被引用的情況。如果計數是0,說明沒有物件引用該物件了,可以被清理;有個問題就是無法解決迴圈引用的問題。
  • 可達性分析
    將物件的引用關係看做是一個圖,將選擇活動中的物件作為根物件GC Root,然後按圖索驥的去查詢。如果一個物件和GC Root之間不可達,也就是不存在引用鏈,那麼久認為這個物件時不可達的,可以被清理。JVM會把Java棧和本地方法棧中正在引用的物件,靜態屬性引用的物件和常量作為GC Root。

清理演算法

  • 標記-複製
    將記憶體區域分成兩塊大小相同的區域(to和from),然後每次都是使用from區域,通過收集演算法,將from中還活著的物件遷移到to區域,並且是順序安放,保證記憶體的連續性,同時將to區域標記為from區域,原來的from區域標記為to區域。
  • 標記-清除 首先進行標記工作,將要清除的物件進行標記,然後直接進行清除,標記和清除的效率比較高,但是會產生記憶體碎片化,如果申請大記憶體,容易引發Full GC,會產生較長時間的STW。
  • 標記-整理 類似於標記-清除,但是為了避免記憶體的碎片化,會在清理過程中將物件往一個方向移動,保證記憶體的連續性。

GC執行過程

GC主要發生的區域是在堆區,所以我們先簡單分析一下堆的結構。借用別人的一張圖:

JVM中記憶體和GC的介紹
安裝上圖,從GC年代的角度劃分,java 堆分為:

  1. 新生代
    新生代是大部分物件的建立和銷燬的區域,因此對新生代也做了劃分:
    • Eden區域
      Eden會繼續劃分,為每個執行緒劃分一個TLAB區域,為了安全,會加鎖進行競爭;這些TLAB都是連續分配的,如果一個TLAB滿了,會繼續給當前執行緒分配新的TLAB。新建立的物件基本都是在當前執行緒所分配的TLAB中,如果需要建立的物件過大,就會直接分配到老年代。
    • Survior區域
      Survior會平均大小的分為兩個區域to和from,然後再GC期間將from和Eden中存活的物件全部遷移到to中,主要是為了避免記憶體的碎片化。
  2. 老年代
    放置長生命週期的物件(經歷了多次GC存活的物件),通常是從Survior中拷貝過來的物件。但是,如果新建立的物件在Eden中無法找到足夠長度的記憶體,也會直接放到老年代。

堆,新生代,老年代的大小都是可以通過jvm引數進行設定的。

具體執行過程:

  1. Java不斷的建立物件,直接分配到Eden區域,如果達到GC設定的閾值,觸發Minor GC。存活下來的會被遷移到Survior中的from區域,同時會對這些物件的生命週期加1,表明存活時間。
  2. 經過第一次的GC後,Eden會空閒下來,此時繼續在Eden上面分配空間,直到再次達到閾值,觸發GC,此時會在Eden和Survior中的from同時清理,然後將餘下的物件複製到to區域裡面,存活時間加1.
  3. 多次發生上述第二步後,當存活物件的存活時間達到閾值時,會發生晉升過程,將達到閾值的物件遷移到老年代。這個閾值也是可以設定的。同樣,如果Survior大小不夠了也會發生晉升。
  4. 老年代同意會進行GC 稱為Major GC,GC的清理演算法根據不同的GC型別而不同,但是大部分是使用的標記-整理,這樣可以避免記憶體的碎片化。

為了形象借用幾張楊曉峰老師的示意圖:

JVM中記憶體和GC的介紹
JVM中記憶體和GC的介紹
JVM中記憶體和GC的介紹

GC的類別

隨著技術的發展,jvm出現了很多不同的垃圾收集器。

  • Serial Old GC:序列的,會觸發Stop-The-World,採用標記-整理,用於老年代。
  • Serial GC :序列的,複製演算法,,會觸發Stop-The-World,適用於單CPU的client環境
  • ParNew GC: 並行的,會觸發Stop-The-World,用於新生代,複製演算法,適用於多核CPU的Server環境。可以簡單的理解為Serial的並行版本。
  • Parallel Scavenge GC:並行的,用於新生代,複製演算法,可以控制吞吐量(使用者程式碼執行時間/(使用者程式碼執行時間+垃圾收集時間)),適用於後臺運算多,互動少的場景。
  • CMS(Concurrent Mark Swap) GC: 併發的,標記階段會觸發STW,採用標記-整理演算法,用於老年代,適用於伺服器
  • G1 GC:jdk9 後預設的GC,替代CMS,對於大記憶體的堆空間,效能會比較優越。

總結一下:

新生代主要會使用的垃圾收集器是Serial,Parallel等
老年代主要是CMS,配合其他型別一起。

相關文章