面試重災區——JVM內存結構和垃圾回收機制

JVM介紹

1. JVM的體系架構(內存模型)

綠色的爲線程私有,橘色的爲線程共有java

2. 類加載器

負責將.class文件加載到內存中,而且將該文件中的數據結構轉換爲方法區中的數據結構,生成一個Class對象算法

2.1 類加載器分類

  • 自啓動類加載器。Bootstrap ClassLoader類加載器。負責加載jdk自帶的包。安全

    • %JAVA_HOME%/lib/rt.jar%即JDK源碼
    • 使用C++編寫
    • 在程序中直接獲取被該加載器加載的類的類加載器會出現null
  • 擴展類加載器.Extension ClassLoader。負責加載jdk擴展的包服務器

    • 便於將來擴展
    • %JAVA_HOME/lib/ext/*.jar%
  • 應用類加載器或系統類加載器。AppClassLoader或SystemClassLOader數據結構

    • 用於加載自定義類的加載器
    • CLASSPATH路徑下
  • 自定義類加載器多線程

    • 經過實現ClassLoader抽象類實現

2.2 雙親委派機制

當應用類加載器獲取到一個類加載的請求的時候,不會當即處理這個類加載請求,而是將這個請求委派給他的父加載器加載,若是這個父加載器不可以處理這個類加載請求,便將之傳遞給子加載器。一級一級傳遞指導能夠加載該類的類加載器。架構

該機制又稱沙盒安全機制。防止開發者對JDK加載作破壞併發

2.3 打破雙親委派機制

  • 自定義類加載器,重寫loadClass方法
  • 使用線程上下文類加載器

2.4 Java虛擬機的入口文件

sun.misc.Launcher函數

3. Execution Engine

執行引擎負責執行解釋命令,交給操做系統進行具體的執行性能

4. 本地接口

4.1 native方法

native方法指Java層面不能處理的操做,只能經過本地接口調用本地的函數庫(C函數庫)

4.2 Native Interface

一套調用函數庫的接口

5. 本地方法棧

在加載native方法的時候,會將執行的C函數庫的方法,放在這個棧區域執行

6. 程序計數器

每一個線程都有程序計數器,主要做用是存儲代碼指令,就相似於一個執行計劃。

內部維護了多個指針,這些指針指向了方法區中的方法字節碼。執行引擎從程序計數器中獲取下一次要執行的指令。

因爲空間很小,他是當前線程執行代碼的一個行號指示器/

不會引起OOM

7. 方法區

供各線程共享的運行時內存區域,存放了各個類的結構信息(一個Class對象),包括:字段,方法,構造方法,運行時常量池。

雖然JVM規範將方法區描述爲堆的一個邏輯部分,但它卻還有一個別名叫作Non-Heap(非堆),目的就是要和堆分開

主要有:永久代或者元空間。存在GC

元空間中因爲直接使用物理內存的影響,因此默認的最大元空間大小爲1/4物理內存大小

8. Java棧

主要負責執行各類方法,是線程私有的,隨線程的消亡而消亡,不存在垃圾回收的問題。八大數據類型和實例引用都是在函數的棧內存中分配內存的。

默認大小爲512~1024K,經過-Xss1024k參數修改

8.1 棧和隊列數據結構

FILO:先進後出

隊列FIFO:先進先出

8.2 存儲的數據

  • 本地變量Local Variable。包括方法的形參和返回值
  • 棧操做Operand Stack。包括各類壓棧和出棧操做
  • 棧幀數據Frame Data。就至關於一個個方法。在棧空間中,方法被稱爲棧幀

8.3 執行流程

棧中執行的單位是棧幀,棧幀就是一個個方法。

  • 首先將main方法壓棧,成爲一個棧幀
  • 而後調用其餘方法,即再次壓棧
  • 棧幀中存儲了這個方法的局部變量表,操做數棧、動態連接、方法出口等
  • 棧的大小和JVM的實現有關,一般在256K~756K

9. 方法區,棧,堆的關係

10. Heap 堆

10.1 堆內存結構

默認初始大小爲物理內存的1/64,默認最大大小爲1/4。在實際生產中通常會將這兩個值設置爲相同,避免垃圾回收器執行完垃圾回收之後還須要進行空間的擴容計算,浪費資源。

堆外內存:內存對象分配在Java虛擬機的堆之外的內存,這些內存直接受操做系統管理(而不是虛擬機),這樣作的結果就是可以在必定程度上減小垃圾回收對應用程序形成的影響。使用未公開的Unsafe和NIO包下ByteBuffer來建立堆外內存。

默認的堆外內存大小爲,經過-XX:MaxDirectMemorySize=執行堆外內存的大小

10.1.1 JDK1.7

在邏輯上劃分爲三個區域:

  • 新生區Young Generation Space

    • 伊甸區Eden Space
    • 倖存區Survivor 0 Space
    • 倖存區Survivor 1 Space
  • 養老區Tenure Generation Space
  • 永久區Permanent Space(方法區)

在物理層面劃分爲兩個區域:

  • 新生區
  • 老年區
10.1.1.1 堆內存GC過程

主要流程有三步:

  • Eden滿了之後出發一次輕GC(Minor GC),沒有死亡的對象,年齡+1,存放到from區域
  • Eden再次滿了之後再次觸發一次GC,沒有死亡的對象放置於to區域,而後將from區域中沒有死亡的對象所有置於to區域,年齡+1。以後每一次GC都會出發一次fromto的交換,哪一個區域是空的那個區域就是to
  • survivor區域滿了之後,再次觸發GC,當存在對象的年齡等於15的時候,就會將該對象移入老年區

    • MaxTenuringThreshold經過這個參數設置當年齡爲多少的時候移入
  • 老年區滿了之後觸發一次Full GC,若是老年區沒法再存放對象直接報OOM

注意:每一次GC都會給存活的對象的年齡+1

10.1.2 JDK1.8

1.7相比,僅僅是將永久代更替爲了元空間。元空間的存放內置是物理內存,而不是JVM中。

這樣處理,可使元空間的大小再也不受虛擬機內存大小的影響,而是由系統當前可用的空間來控制。

新生區和老年區的大小比例爲1:2,經過-XX:NewRatio=n設置新生代和老年代的比例,n表明老年區所佔的比例。

Eden Space和Survivor Space之間的比例默認爲8:1,經過-XX:SurvivorRatio設置伊甸區和倖存者區的比例

邏輯層面分層:

  • 新生區Young Generation Space

    • 伊甸區Eden Space
    • 倖存區Survivor 0 Space
    • 倖存區Survivor 1 Space
  • 老年區Tenure Generation Space
  • 元空間(方法區)

物理層面分層:

  • 新生區 他佔據堆的1/3
  • 老年區 他佔據堆的2/3

10.2 堆參數調優

10.2.1 經常使用堆參數
參數 做用
-Xms 設置初始堆大小,默認爲物理內存的1/64
-Xmx 設置最大堆大小,默認爲物理內存的1/4
-XX:+PrintGCDetails 輸出詳細的GC日誌

模擬OOM

//設置最大堆內存爲10m 
//-Xms10m -Xmx10m -XX:+PrintGCDetails

下面咱們具體分析一下GC的過程作了什麼,GC日誌怎麼看

名稱:GC之前佔用->GC以後佔用(總共佔用)

//GC 分配失敗
GC (Allocation Failure)
    [PSYoungGen: 1585K->504K(2560K)] 1585K->664K(9728K), 0.0009663 secs] //[新生代,之前佔用->線程佔用(總共空閒)] 堆使用大小->堆如今大小(總大小)
    [Times: user=0.00 sys=0.00, real=0.00 secs] 
    
    
[Full GC (Allocation Failure)
 [PSYoungGen: 0K->0K(2560K)] 
 [ParOldGen: 590K->573K(7168K)] 590K->573K(9728K),
 [Metaspace: 3115K->3115K(1056768K)], 0.0049775 secs] 
 [Times: user=0.00 sys=0.00, real=0.01 secs]

11. 垃圾回收算法

11.1 垃圾回收類型

  • 普通GC(minor GC)發生在新生區的,很頻繁
  • 全局GCmajor GC發生在老年代的垃圾收集動做,出現一次major GC常常會伴隨至少一次的Minor GC

11.2 垃圾回收算法分類

11.2.1 引用計數法

主要思想:每存在一個對象引用就給這個對象加一,當這個對象的引用爲零的時候,便觸發垃圾回收。通常不使用

缺點:

  • 每次新建立對象就須要添加一個計數器,比較浪費
  • 循環引用較難處理
11.2.2 複製算法

主要思想:將對象直接拷貝一份,放置到其餘區域

優勢:不會產生內存碎片

缺點:佔用空間比較大

使用場景:新生區的複製就是經過複製算法來執行的。當Minor Gc之後,就會倖存的對象複製一份放置到to

11.2.3 標記清除算法

主要思想:從引用根節點遍歷全部的引用,標記出全部須要清理的對象,而後進行清除。兩步完成

缺點:在進行垃圾回收的時候會打斷整個代碼的運行。會產生內存碎片

11.2.4 標記整理算法

主要思想:和標記清除算法同樣,最後添加了一個步驟整理,將整理內存碎片。三步完成

缺點:效率低,須要移動對象。

11.3 各大垃圾回收算法比較

11.3.1 內存效率

複製算法>標記清除法>標記整理法

11.3.2 內存整齊度

複製算法=標記整理法>標記清除法

11.3.3 內存利用率

標記整理法=標記清除法>複製算法

11.3.4 最優算法

經過場景使用不一樣的算法,來達到最優的目的

年輕代:由於其對象存活時間段,對象死亡率高,因此通常使用複製算法

老年代:區域大,存活率高,通常採用標記清除和標記整理的混合算法。

老年代通常是由標記清除或者是標記清除與標記整理的混合實現。以hotspot中的CMS回收器爲例,CMS是基於Mark-Sweep實現的,對於對像的回收效率很高,而對於碎片問題,CMS採用基於Mark-Compact算法的Serial Old回收器作爲補償措施:當內存回收不佳(碎片致使的Concurrent Mode Failure時),將採用Serial Old執行Full GC以達到對老年代內存的整理。

11.3.5 GCRoots

上面咱們提到標記清除算法的時候,提到了一個名詞,根節點引用。那麼什麼叫作根節點引用呢?

根節點引用也成GCRoots,他是指垃圾回收算法進行對象遍歷的根節點。即從這個對象開始往下遍歷,標記須要進行回收的對象。

垃圾回收標記的過程就是:以GCRoots對象開始向下搜索,若是一個對象到GCRoots沒有任何的引用鏈相連時,說明此對象不可用。

就是從GCRoots進行遍歷,能夠被遍歷到的就不是垃圾,沒有被遍歷到的就是垃圾,斷定死亡

11.3.5.1 可達性對象和不可達性對象

可達性對象是指,在對象鏈路引用的頂層是一個GCRoot引用

不可達對象是指,在對象鏈路引用的頂層不是一個GCRoot引用

通俗解釋:可達性對象就是對象有一個歸屬,這個歸屬有一個術語名稱叫作GCRoot,不可達性對象就是這些對象沒有歸屬。

11.3.5.2 什麼引用能夠做爲GCRoots
  • 棧內的局部變量引用
  • 元空間中的靜態屬性引用
  • 元空間中的常量引用
  • 本地方法棧中native修飾的方法

說白了,就是全部暴露給開發者的引用

12. 垃圾回收器

垃圾回收器是基於GC算法實現的。

主要有四種垃圾回收器,不過具體有七種使用方式

12.1 四種垃圾回收器

12.1.1 串行垃圾回收器(Serial)

單線程進行垃圾回收,此時其餘的線程所有被暫停

經過-XX:+UseSerialGC

12.1.2 並行垃圾回收器(Parallel)

多線程進行垃圾回收,此時其餘的線程所有被暫停

12.1.3 併發垃圾回收器(CMS)

GC線程和用戶線程同時運行

12.1.4 G1垃圾回收器

分區垃圾回收。物理上不區分新生區和養老區,將堆內存劃分爲1024個小的region,每個佔據的空間在2~32M,每個region均可能是Eden SpaceSurvivor01 SpaceSurvivor02 SpaceOld區。

總體使用了標記整理算法,局部使用了複製算法。經過複製算法將GC後的對象從一個region向另外一個region遷移,至於形成了內存碎片問題,經過總體的標記整理算法,避免了內存碎片的誕生

在進行垃圾回收的時候直接對一個region進行回收,保存下來的對象經過複製算法複製到TO區或者Old區。

邏輯上堆有四個區,每個區的大小不定,按需分配。分爲Eden SpaceSurvivor01 SpaceOldHumongous。其中Humongous用來存放大對象,通常是連續存儲,當因爲連續region不足的時候,會觸發Full GC清理周圍的Region以存放大對象

G1堆內存示意

G1垃圾回收

出現大對象,三個region不能存放,進行FullGC

執行流程

  • 初始標記。GC多線程,標記GCRoots
  • 併發標記。用戶線程和GC線程同時進行。GC線程遍歷GCRoots的全部的對象,進行標記
  • 從新標記。修正被併發標記標記的對象,因爲用戶程序再次調用,而須要取消標記的對象。GC線程
  • 篩選回收。清理被標記的對象。GC線程
  • 用戶線程繼續運行

12.1.4.1 案例
  • 初始標記。是經過一個大對象引起的G1

  • 併發標記

  • 從新標記、篩選清理和大對象引起的Full GC

12.1.4.2 G1經常使用參數
-XX:+UseG1GC  開啓GC
-XX:G1HeapRegionSize=n : 設置G1區域的大小。值是2的冪,範圍是1M到32M。目標是根據最小的Java堆大小劃分出約2048個區域
-XX:MaxGCPauseMillis=n : 最大停頓時間,這是個軟目標,JVM將盡量(但不保證)停頓時間小於這個時間
    
-XX:InitiatingHeapOccupancyPercent=n  堆佔用了多少的時候就觸發GC,默認是45
-XX:ConcGCThreads=n  併發GC使用的線程數
-XX:G1ReservePercent=n 設置做爲空閒空間的預留內存百分比,以下降目標空間溢出的風險,默認值是10%

12.2 經常使用參數

DefNew      Default New Generation //串行垃圾回收器,新生代叫法
Tenured     Old  //串行垃圾回收器,老年代叫法
ParNew         Parallel New Generation //新生代並行垃圾回收器,新生代叫法
PSYongGen     Parallel Scavenge //新生代和老年代垃圾回收器,叫法
ParOldGen     Parallel Old Generation //新生代和老年代垃圾回收器,叫法

12.3 新生代垃圾回收器

上圖顯示的是新生區和老年區可使用垃圾回收器的全部種類,咱們一個一個來講明

12.3.1 串行GC(Serial/Serial Coping)

新生代使用Serial Coping垃圾回收器使用複製算法

老年區默認使用Serial Old垃圾回收器,使用標記清除算法和標記整理算法

經過-XX:+UseSerialGC設置

12.3.2 並行GC(ParNew)

新生區使用ParNew垃圾回收器,使用複製算法

老年區使用Serial Old垃圾回收器(不推薦這樣使用),使用標記清除算法和標記整理算法

經過-XX:+UseParNewGC啓動

12.3.3 並行回收GC(Parallel/Parallel Scavenge)

新生代使用並行垃圾回收

老年代使用並行垃圾回收。Java1.8中默認使用的垃圾回收器

一個問題:Parallel和Parallel Scavenge收集器的區別?

Parallel Scavenge收集器相似於ParNew也是一個新生代的垃圾收集器,使用了複製算法,也是一個並行的多線程的垃圾收集器,俗稱吞吐量優先收集器。

parallel Scavenge是一種自適應的收集器,虛擬機會根據當前系統運行狀況收集性能監控信息,動態調整這些參數以提供最合適的提頓時間或者最大吞吐量

他關注的點是:

可控制的吞吐量。吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),

同時,當新生代選擇爲Parallel Scavenge的時候,會默認激活老年區使用並行垃圾回收

經過-XX:UseParallelGC或者-XX:UseParallelOldGC二者會互相激活

-XX:ParallelGCThreads=n表示啓動多少個GC線程

cpu>8時 N=5或者8

cpu<8時 N=實際個數

12.4 老年代垃圾回收器

12.4.1 串行垃圾回收器(Serial Old/Serial MSC)

Serial OldSerial垃圾收集器老年代版本,是一個單線程的收集器,使用標記整理算法,運行在Client中的年老代垃圾回收算法

與新生代的Serial GC相關聯

12.4.2 並行回收(Parallel Old/Parallel MSC)

Parallel Old/採用標記整理算法實現

與新生代的Parallel Scavenge GC相關聯

12.4.3 併發標記清除GC

CMS收集器(Concurrent Mark Sweep併發標記清除):一種以獲取最短回收停頓時間爲目標的收集器

適合應用在互聯網站或者B/S系統的服務器上,重視服務器的響應速度

CMS很是適合堆內存大、CPU核數多的服務端應用,也是G1出現以前大型應用的首選收集器

標記的時候,GC線程運行;清除的時候和用戶線程一塊兒運行

經過-XX:+UseConcMarkSweepGC指令開啓

配合新生區的pallellal New GC回收器使用

當CMS因爲CPU壓力太大沒法使用的時候會使用SerialGC做爲備用收集器

12.4.3.1 CMS執行過程
  • 初始標記(CMS initial mark)。遍歷尋找到全部的GCRootsGC線程執行,用戶線程暫停
  • 併發標記(CMS concurrent mark)和用戶線程一塊兒遍歷GCRoots,標記須要清除的對象
  • 從新標記(CMS remark)。修正標記期間,對因用戶程序繼續運行而不須要進行回收的對象進行修正
  • 併發清除(CMS concurrent sweep)和用戶線程一塊兒清除全部標記的對象

12.4.3.2 優缺點

優勢:

  • 併發收集低停頓

缺點:

  • 併發執行,對CPU資源壓力大
  • 採用標記清除算法會致使大量的內存碎片

12.5 垃圾回收器小結

參數(-XX:+……) 新生代垃圾回收器 新生代算法 老年代垃圾回收器 老年代算法
UseSerialGC SerialGC 複製算法 Serial Old GC 標整
UseParNewGC Parallel New GC 複製算法 Serial Old GC 標整
UseParllelGC Parallel Scavenge GC 複製算法 Parallel GC 標整
UseConcMarkSweepGC Parallel New GC 複製算法 CMS和Serial Old GC 標清
UseG1GC 總體標整 局部複製

垃圾回收算法通用邏輯

12.6 CMS和G1的區別

  • G1不會引起內存碎片
  • G1對內存的精準控制,能夠精準的去收集垃圾。根據設置的GC處理時間去收集垃圾最多的區域

13. JMM

java內存模型。是一種規範。

線程在操做變量的時候,首先從物理內存中複製一份到本身的工做內存中(棧內存),更新之後再寫入物理內存中

特色:

  • 原子性
  • 可見性
  • 有序性

更多原創文章和學習教程請關注筆者同名公衆號@MakerStack獲取
相關文章
相關標籤/搜索