java面試題之----JVM架構和GC垃圾回收機制詳解

JVM架構和GC垃圾回收機制詳解

jvm,jre,jdk三者之間的關係

JRE (Java Run Environment):JRE包含了java底層的類庫,該類庫是由c/c++編寫實現的java

JDK (Java Development kit) 即java開發工具包,jdk包含了jre和一些java開發工具包,即java基礎類庫rt.jarc++

JVM(Java Virtual Machine) 即java虛擬機,Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的可以「一次編譯,處處運行」的緣由。算法

 

JVM架構圖分析

下圖:參考網絡+書籍,若有侵權請見諒 想了解Hadoop內存溢出請看: Hadoop內存溢出(OOM)分類、參數調優化小程序

JVM被分爲三個主要的子系統

(1)類加載器子系統(2)運行時數據區(3)執行引擎數組

1. 類加載器子系統

Java的動態類加載功能是由類加載器子系統處理。當它在運行時(不是編譯時)首次引用一個類時,它加載、連接並初始化該類文件。安全

1.1 加載

類由此組件加載。啓動類加載器 (BootStrap class Loader)、擴展類加載器(Extension class Loader)和應用程序類加載器(Application class Loader) 這三種類加載器幫助完成類的加載。性能優化

1.  啓動類加載器 – 負責從啓動類路徑中加載類,無非就是rt.jar。這個加載器會被賦予最高優先級。網絡

2.  擴展類加載器 – 負責加載ext 目錄(jre\lib)內的類.數據結構

3.  應用程序類加載器 – 負責加載應用程序級別類路徑,涉及到路徑的環境變量等etc.多線程

上述的類加載器會遵循委託層次算法(Delegation Hierarchy Algorithm)加載類文件

1.2 連接

1.  校驗 – 字節碼校驗器會校驗生成的字節碼是否正確,若是校驗失敗,咱們會獲得校驗錯誤

2.  準備 – 分配內存並初始化默認值給全部的靜態變量。

3.  解析 – 全部符號內存引用方法區(Method Area)原始引用所替代。

1.3 初始化

這是類加載的最後階段,這裏全部的靜態變量會被賦初始值, 而且靜態塊將被執行。

2. 運行時數據區(Runtime Data Area)

The 運行時數據區域被劃分爲5個主要組件:

2.1 方法區(Method Area)

全部類級別數據將被存儲在這裏,包括靜態變量。每一個JVM只有一個方法區,它是一個共享的資源。

2.2 堆區(Heap Area)

全部的對象和它們相應的實例變量以及數組將被存儲在這裏。每一個JVM一樣只有一個堆區。因爲方法區堆區的內存由多個線程共享,因此存儲的數據不是線程安全的

2.3 棧區(Stack Area)

對每一個線程會單首創建一個運行時棧。對每一個函數呼叫會在棧內存生成一個棧幀(Stack Frame)。全部的局部變量將在棧內存中建立。棧區是線程安全的,由於它不是一個共享資源。棧幀被分爲三個子實體:

a 局部變量數組 – 包含多少個與方法相關的局部變量而且相應的值將被存儲在這裏。

b 操做數棧 – 若是須要執行任何中間操做,操做數棧做爲運行時工做區去執行指令。

c 幀數據 – 方法的全部符號都保存在這裏。在任意異常的狀況下,catch塊的信息將會被保存在幀數據裏面。

2.4 PC寄存器

每一個線程都有一個單獨的PC寄存器來保存當前執行指令的地址,一旦該指令被執行,pc寄存器會被更新至下條指令的地址。

2.5 本地方法棧

本地方法棧保存本地方法信息。對每個線程,將建立一個單獨的本地方法棧。

3. 執行引擎

分配給運行時數據區的字節碼將由執行引擎執行。執行引擎讀取字節碼並逐段執行。

3.1  解釋器:

 解釋器能快速的解釋字節碼,但執行卻很慢。 解釋器的缺點就是,當一個方法被調用屢次,每次都須要從新解釋。

編譯器

JIT編譯器消除了解釋器的缺點。執行引擎利用解釋器轉換字節碼,但若是是重複的代碼則使用JIT編譯器將所有字節碼編譯成本機代碼。本機代碼將直接用於重複的方法調用,這提升了系統的性能。

a. 中間代碼生成器 – 生成中間代碼

b. 代碼優化器 – 負責優化上面生成的中間代碼

c. 目標代碼生成器 – 負責生成機器代碼或本機代碼

d.  探測器(Profiler) – 一個特殊的組件,負責尋找被屢次調用的方法。

3.3  垃圾回收器:

收集並刪除未引用的對象。能夠經過調用"System.gc()"來觸發垃圾回收,但並不保證會確實進行垃圾回收。JVM的垃圾回收只收集哪些由new關鍵字建立的對象。因此,若是不是用new建立的對象,你可使用finalize函數來執行清理。

Java本地接口 (JNI)JNI 會與本地方法庫進行交互並提供執行引擎所需的本地庫。

本地方法庫:它是一個執行引擎所需的本地庫的集合。

JVM三大核心區域

經過一個小程序認識JVM

  1. package com.spark.jvm;
  2. /**
  3. * 從JVM調用的角度分析java程序堆內存空間的使用:
  4. * 當JVM進程啓動的時候,會從類加載路徑中找到包含main方法的入口類HelloJVM
  5. * 找到HelloJVM會直接讀取該文件中的二進制數據,而且把該類的信息放到運行時的Method內存區域中。
  6. * 而後會定位到HelloJVM中的main方法的字節碼中,並開始執行Main方法中的指令
  7. * 此時會建立Student實例對象,而且使用student來引用該對象(或者說給該對象命名),其內幕以下:
  8. * 第一步:JVM會直接到Method區域中去查找Student類的信息,此時發現沒有Student類,就經過類加載器加載該Student類文件;
  9. * 第二步:在JVM的Method區域中加載並找到了Student類以後會在Heap區域中爲Student實例對象分配內存,
  10. * 而且在Student的實例對象中持有指向方法區域中的Student類的引用(內存地址);
  11. * 第三步:JVM實例化完成後會在當前線程中爲Stack中的reference創建實際的應用關係,此時會賦值給student
  12. * 接下來就是調用方法
  13. * 在JVM中方法的調用必定是屬於線程的行爲,也就是說方法調用自己會發生在線程的方法調用棧:
  14. * 線程的方法調用棧(Method Stack Frames),每個方法的調用就是方法調用棧中的一個Frame,
  15. * 該Frame包含了方法的參數,局部變量,臨時數據等 student.sayHello();
  16. */
  17. public class HelloJVM {
  18. //在JVM運行的時候會經過反射的方式到Method區域找到入口方法main
  19. public static void main(String[] args) {//main方法也是放在Method方法區域中的
  20. /**
  21. * student(小寫的)是放在主線程中的Stack區域中的
  22. * Student對象實例是放在全部線程共享的Heap區域中的
  23. */
  24. Student student = new Student("spark");
  25. /**
  26. * 首先會經過student指針(或句柄)(指針就直接指向堆中的對象,句柄代表有一箇中間的,student指向句柄,句柄指向對象)
  27. * 找Student對象,當找到該對象後會經過對象內部指向方法區域中的指針來調用具體的方法去執行任務
  28. */
  29. student.sayHello();
  30. }
  31. }
  32.  
  33. class Student {
  34. // name自己做爲成員是放在stack區域的可是name指向的String對象是放在Heap中
  35. private String name;
  36. public Student(String name) {
  37. this.name = name;
  38. }
  39. //sayHello這個方法是放在方法區中的
  40. public void sayHello() {
  41. System.out.println( "Hello, this is " + this.name);
  42. }
  43. }

JVM三大性能調優參數:-Xms –Xmx –Xss

-Xms –Xmx是對堆的性能調優參數,通常兩個設置是同樣的,若是不同,當Heap不夠用,會發生內存抖動。通常都調大這兩個參數,而且兩個大小同樣。

-Xss是對每個線程棧的性能調優參數,影響堆棧調用的深度

實戰演示從OOM推導出JVM GC時候基於的內存結構:Young Generation(Eden、From、To)、OldGeneration、Permanent Generation


JVMHeap區域(年輕代、老年代)和方法區(永久代)結構圖:

從Java GC的角度解讀代碼:程序20行new的Person對象會首先會進入年輕代的Eden中(若是對象太大可能直接進入年老代)。在GC以前對象是存在Eden和from中的,進行GC的時候Eden中的對象被拷貝到To這樣一個survive空間(survive倖存)空間:包括from和to,他們的空間大小是同樣的,又叫s1和s2)中(有一個拷貝算法),From中的對象(算法會考慮通過GC倖存的次數)到必定次數(閾值(若是說每次GC以後這個對象依舊在Survive中存在,GC一次他的Age就會加1,默認15就會放到OldGeneration。可是實際狀況比較複雜,有可能沒有到閾值就從Survive區域直接到Old Generation區域。在進行GC的時候會對Survive中的對象進行判斷,Survive空間中有一些對象Age是同樣的,也就是通過的GC次數同樣,年齡相同的這樣一批對象的總和大於等於Survive空間一半的話,這組對象就會進入old Generation中,(是一種動態的調整))),會被複制到OldGeneration,若是沒到次數From中的對象會被複制到To中,複製完成後To中保存的是有效的對象,Eden和From中剩下的都是無效的對象,這個時候就把Eden和From中全部的對象清空。在複製的時候Eden中的對象進入To中,To可能已經滿了,這個時候Eden中的對象就會被直接複製到Old Generation中,From中的對象也會直接進入Old Generation中。就是存在這樣一種狀況,To比較小,第一次複製的時候空間就滿了,直接進入old Generation中。複製完成後,To和From的名字會對調一下,由於Eden和From都是空的,對調後Eden和To都是空的,下次分配就會分配到Eden。一直循環這個流程。好處:使用對象最多和效率最高的就是在Young Generation中,經過From to就避免過於頻繁的產生FullGC(Old Generation滿了通常都會產生FullGC)

虛擬機在進行MinorGC(新生代的GC)的時候,會判斷要進入OldGeneration區域對象的大小,是否大於Old Generation剩餘空間大小,若是大於就會發生Full GC。

剛分配對象在Eden中,若是空間不足嘗試進行GC,回收空間,若是進行了MinorGC空間依舊不夠就放入Old Generation,若是OldGeneration空間還不夠就OOM了。

比較大的對象,數組等,大於某值(可配置)就直接分配到老年代,(避免頻繁內存拷貝)

年輕代和年老代屬於Heap空間的

Permanent Generation(永久代)能夠理解成方法區,(它屬於方法區)也有可能發生GC,例如類的實例對象所有被GC了,同時它的類加載器也被GC掉了,這個時候就會觸發永久代中對象的GC。

若是OldGeneration滿了就會產生FullGC

滿緣由:1,from survive中對象的生命週期到必定閾值

2,分配的對象直接是大對象

三、因爲To 空間不夠,進行GC直接把對象拷貝到年老代(年老代GC時候採用不一樣的算法)

若是Young Generation大小分配不合理或空間比較小,這個時候致使對象很容易進入Old Generation中,而Old Generation中回收具體對象的時候速度是遠遠低於Young Generation回收速度。

所以實際分配要考慮年老代和新生代的比例,考慮Eden和survives的比例

Permanent Generation中發生GC的時候也對性能影響很是大,也是Full GC

JVM GC時候核心參數:

-XX:NewRatio –XX:SurvivorRatio –XX:NewSize –XX:MaxNewSize

–XX:NewSize–XX:MaxNewSize指定新生代初始大小和最大大小。

1,-XX:NewRatio    是年老代 新生代相對的比例,好比NewRatio=2,代表年老代是新生代的2倍。老年代佔了heap的2/3,新生代佔了1/3

2,-XX:SurvivorRatio 配置的是在新生代裏面Eden和一個Servive的比例

若是指定NewRatio還能夠指定NewSizeMaxNewSize,若是同時指定了會如何???

NewRatio=2,這個時候新生代會嘗試分配整個Heap大小的1/3的大小,可是分配的空間不會小於-XX:NewSize也不會大於 –XX:MaxNewSize

3,-XX:NewSize –XX:MaxNewSize

實際設置比例仍是設置固定大小,固定大小理論上速度更高。

-XX:NewSize –XX:MaxNewSize理論越大越好,可是整個Heap大小是有限的,通常年輕代的設置大小不要超過年老代。

-XX:SurvivorRatio新生代裏面Eden和一個Servive的比例,若是SurvivorRatio是5的話,也就是Eden區域是SurviveTo區域的5倍。Survive由From和To構成。結果就是整個Eden佔用了新生代5/7,From和To分別佔用了1/7,若是分配不合理,Eden太大,這樣產生對象很順利,可是進行GC有一部分對象倖存下來,拷貝到To,空間小,就沒有足夠的空間,對象會被放在old Generation中。若是Survive空間大,會有足夠的空間容納GC後存活的對象,可是Eden區域小,會被很快消耗完,這就增長了GC的次數。

JVM的GC日誌解讀:

1、 JVM YoungGeneration下MinorGC日誌詳解

[GC (Allocation Failure) [PSYoungGen:2336K->288K(2560K)] 8274K->6418K(9728K), 0.0112926 secs] [Times:user=0.06 sys=0.00, real=0.01 secs]

PSYoungGen(是新生代類型,新生代日誌收集器),2336K表示使用新生代GC前,佔用的內存,->288K表示GC後佔用的內存,(2560K)表明整個新生代總共大小

8274K(GC前整個JVM Heap對內存的佔用)->6418K(MinorGC後內存佔用總量)(9728K)(整個堆的大小)0.0112926 secs(Minor GC消耗的時間)] [Times: user=0.06 sys=0.00, real=0.01 secs] 用戶空間,內核空間時間的消耗,real整個的消耗

2、 JVM的GC日誌Full GC日誌每一個字段完全詳解

[Full GC (Ergonomics) [PSYoungGen: 984K->425K(2048K)] [ParOldGen:7129K->7129K(7168K)] 8114K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1022588 secs] [Times: user=0.56 sys=0.02,real=0.10 secs]

[Full GC (Allocation Failure) [PSYoungGen: 425K->425K(2048K)][ParOldGen: 7129K->7129K(7168K)] 7555K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1003696 secs] [Times: user=0.64 sys=0.03,real=0.10 secs]

[Full GC(代表是Full GC) (Ergonomics) [PSYoungGen:FullGC會致使新生代Minor GC產生]984K->425K(2048K)][ParOldGen:(老年代GC)7129K(GC前多大)->7129K(GC後,並無下降內存佔用,由於寫的程序不斷循環一直有引用)(7168K) (老年代總容量)] 8114K(GC前佔用整個Heap空間大小)->7555K (GC後佔用整個Heap空間大小) (9216K) (整個Heap大小,JVM堆的大小), [Metaspace: (java6 7是permanentspace,java8改爲Metaspace,類相關的一些信息) 2613K->2613K(1056768K) (GC先後基本沒變,空間很大)], 0.1022588 secs(GC的耗時,秒爲單位)] [Times: user=0.56 sys=0.02, real=0.10 secs](用戶空間耗時,內核空間耗時,真正的耗時時間)

3、 Java8中的JVM的MetaSpace

Metaspace的使用C語言實現的,使用的是OS的空間,Native Memory Space可動態的伸縮,能夠根據類加載的信息的狀況,在進行GC的時候進行調整自身的大小,來延緩下一次GC的到來。

能夠設置Metaspace的大小,若是超過最大大小就會OOM,不設置若是把整個操做系統的內存耗盡了出現OOM,通常會設置一個足夠大的初始值,安全其間會設置最大值。

永久代發生GC有兩種狀況,類的全部的實例被GC掉,且class load不存。

對於元數據空間 簡化了GC, class load不存在了就須要進行GC。

三種基本的GC算法基石

1、 標記/清除算法

內存中的對象構成一棵樹,當有效的內存被耗盡的時候,程序就會中止,作兩件事,第一:標記,標記從樹根可達的對象(途中水紅色),第二:清除(清楚不可達的對象)。標記清除的時候有中止程序運行,若是不中止,此時若是存在新產生的對象,這個對象是樹根可達的,可是沒有被標記(標記已經完成了),會清除掉。

缺點:遞歸效率低性能低;釋放空間不連續容易致使內存碎片;會中止整個程序運行;

2、 複製算法

把內存分紅兩塊區域:空閒區域和活動區域,第一仍是標記(標記誰是可達的對象),標記以後把可達的對象複製到空閒區,將空閒區變成活動區,同時把之前活動區對象1,4清除掉,變成空閒區。

速度快但耗費空間,假定活動區域所有是活動對象,這個時候進行交換的時候就至關於多佔用了一倍空間,可是沒啥用。

3、 標記整理算法

平衡點

標記誰是活躍對象,整理,會把內存對象整理成一課樹一個連續的空間,

JVM垃圾回收分代收集算法

綜合了上述算法優略

1, 分代GC在新生代的算法:採用了GC的複製算法,速度快,由於新生代通常是新對象,都是瞬態的用了可能很快被釋放的對象。

2, 分代GC在年老代的算法 標記/整理算法,GC後會執行壓縮,整理到一個連續的空間,這樣就維護着下一次分配對象的指針,下一次對象分配就能夠採用碰撞指針技術,將新對象分配在第一個空閒的區域。

JVM垃圾回收器串行、並行、併發垃圾回收器概述

1, JVM中不一樣的垃圾回收器

2, 串行,並行,併發垃圾回收器(和JVM歷史有關係,剛開始串行)

Java中Stop-The-World機制簡稱STW,是在執行垃圾收集算法時,Java應用程序的其餘全部線程都被掛起(除了垃圾收集幫助器以外)。Java中一種全局暫停現象,全局停頓,全部Java代碼中止,native代碼能夠執行,但不能與JVM交互;這些現象多半是因爲gc引發。

JVM中Serial收集器、ParNew收集器、Parallel收集器解析

Serial收集器 單線程方式(沒有線程切換開銷,若是受限物理機器單線程可採用)串行且採用stop the world在工做的時候程序會中止

Serial和serial old

ParNew收集器:多線程(多CPU和多Core的環境中高效),生產環境對低延時要求高的話,就採用ParNew和CMS組合來進行server端的垃圾回收

Parallel 收集器:多線程,並行, 它能夠控制JVM吞吐量的大小,吞吐量優先的收集器,通常設置1%,可設置程序暫停的時間,會經過把新生代空間變小,來完成回收,頻繁的小規模垃圾回收,會影響程序吞吐量大小

JVM中CMS收集器解密

低延遲進行垃圾回收,在線服務和處理速度要求高的狀況下很重要

配置:XX:UseConcMarkSweepGC

concurrence(併發) Mark(標記)Sweep(清理)

低延時

把垃圾回收分紅四個階段

CMS-initial-mark初始標記階段會stop the world,短暫的暫停程序根據跟對象標記的對象所鏈接的對象是否可達來標記出哪些可到達

CMS-concurrent-mark併發標記,根據上一次標記的結果肯定哪些不可到達,線程併發或交替之行,基本不會出現程序暫停。

CMS-remark再次標記,會出現程序暫停,全部內存那一時刻靜止,確保被所有標記,有可能第二階段以前有可能被標記爲垃圾的對象有可能被引用,在此標記確認。

CMS-concurrent-sweep併發清理垃圾,把標記的垃圾清理掉了,沒有壓縮,有可能產生內存碎片,不連續的內存塊,這時候就不能更好的使用內存,能夠經過一個參數配置,根據內存的狀況執行壓縮。

JVM中G1收集器

能夠像CMS收集器同樣,GC操做與應用的現場一塊兒併發執行

緊湊的空閒內存區域且沒有很長的GC停頓時間

須要可預測的GC暫停耗時

不想犧牲太多吞吐量性能

啓動後不須要請求更大的Java堆

經過案例瞬間理解JVM中PSYoungGen、ParOldGen、MetaSpace

  1. Heap
  2. PSYoungGen total 2560K, used 321K[0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
  3. eden space 2048K, 15% used[0x00000007bfd00000,0x00000007bfd50568,0x00000007bff00000)
  4. from space 512K, 0% used[0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  5. to space 512K, 0% used[0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  6. ParOldGen total 7168K, used 7097K[0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
  7. object space 7168K, 99%used [0x00000007bf600000,0x00000007bfcee7b8,0x00000007bfd00000)
  8. Metaspace used 2647K, capacity 4486K, committed4864K, reserved 1056768K
  9. class space used 289K, capacity 386K, committed 512K,reserved 1048576K

PSYoungGen是eden + from

使用MAT對Dump文件進行分析實戰

導出Dump文件

MapReduce過程詳解及其性能優化

相關文章
相關標籤/搜索