天天看點

Java面試題JVM篇

作者:CameSawConquer

1、知識點彙總

JVM是Java運作基礎,面試時一定會遇到JVM的有關問題,内容相對集中,但對知識深度要求較高。

Java面試題JVM篇

知識彙總圖

其中記憶體模型,類加載機制,GC是重點方面.性能調優部分更偏向應用,重點突出實踐能力.編譯器優化和執行模式部分偏向于理論基礎,重點掌握知識點.

需了解 記憶體模型各部分作用,儲存哪些資料;

類加載雙親委派加載機制,常用加載器分别加載哪種類型的類.;

GC分代回收的思想和依據以及不同垃圾回收算法的回收思路和适合場景;

性能調優常有JVM優化參數作用,參數調優的依據,常用的JVM分析工具能分析哪些問題以及使用方法;

執行模式解釋/編譯/混合模式的優缺點,Java7提供的分層編譯技術,JIT即時編譯技術,OSR棧上替換,C1/C2編譯器針對的場景,C2針對的是server模式,優化更激進.新技術方面Java10的graal編譯器

編譯器優化javac的編譯過程,ast抽象文法樹,編譯器優化和運作器優化。

2、知識點詳解

1、JVM記憶體模型:

線程獨占:棧,本地方法棧,程式計數器 線程共享:堆,方法區;

2、棧:

又稱方法棧,線程私有的,線程執行方法是都會建立一個棧陣,用來存儲局部變量表,操作棧,動态連結,方法出口等資訊.調用方法時執行入棧,方法傳回式執行出棧。

3、本地方法棧

與棧類似,也是用來儲存執行方法的資訊.執行Java方法時使用棧,執行Native方法時使用本地方法棧。

4、程式計數器

儲存着目前線程執行的位元組碼位置,每個線程工作時都有獨立的計數器,隻為執行Java方法服務,執行Native方法時,程式計數器為空。

5、堆

JVM記憶體管理最大的一塊,對被線程共享,目的是存放對象的執行個體,幾乎所有的對象執行個體都會放在這裡,當堆沒有可用空間時,會抛出OOM異常.根據對象的存活周期不同,JVM把對象進行分代管理,由垃圾回收器進行垃圾的回收管理;

6、方法區:

又稱非堆區,用于存儲已被虛拟機加載的類資訊,常量,靜态變量,即時編譯器優化後的代碼等資料.1.7的永久代和1.8的元空間都是方法區的一種實作。

7、JVM 記憶體可見性

Java面試題JVM篇

記憶體可見性圖

JMM是定義程式中變量的通路規則,線程對于變量的操作隻能在自己的工作記憶體中進行,而不能直接對主記憶體操作.由于指令重排序,讀寫的順序會被打亂,是以JMM需要提供原子性,可見性,有序性保證。

Java面試題JVM篇

3、說說類加載與解除安裝

加載過程

Java面試題JVM篇

其中驗證,準備,解析合稱連結

加載通過類的完全限定名,查找此類位元組碼檔案,利用位元組碼檔案建立Class對象;

驗證確定Class檔案符合目前虛拟機的要求,不會危害到虛拟機自身安全;

準備進行記憶體配置設定,為static修飾的類變量配置設定記憶體,并設定初始值(0或null).不包含fifinal修飾的靜态變量,因為fifinal變量在編譯時配置設定;

解析将常量池中的符号引用替換為直接引用的過程.直接引用為直接指向目标的指針或者相對偏移量等;

初始化主要完成靜态塊執行以及靜态變量的指派.先初始化父類,再初始化目前類.隻有對類主動使用時才會初始化;

觸發條件包括,建立類的執行個體時,通路類的靜态方法或靜态變量的時候,使用Class.forName反射類的時候,或者某個子類初始化的時候。Java自帶的加載器加載的類,在虛拟機的生命周期中是不會被解除安裝的,隻有使用者自定義的加載器加載的類才可以被卸。

1、加載機制-雙親委派模式

雙親委派模式,即加載器加載類時先把請求委托給自己的父類加載器執行,直到頂層的啟動類加載器。父類加載器能夠完成加載則成功傳回,不能則子類加載器才自己嘗試加載.*

優點:

1. 避免類的重複加載

2. 避免Java的核心API被篡改

2、分代回收

分代回收基于兩個事實:大部分對象很快就不使用了,還有一部分不會立即無用,但也不會持續很長時間。

Java面試題JVM篇

年輕代->标記-複制 老年代->标記-清除

3、回收算法

a、G1算法1.9後預設的垃圾回收算法,特點保持高回收率的同時減少停頓.采用每次隻清理一部分,而不是清理全部的增量式清理,以保證停頓時間不會過長其取消了年輕代與老年代的實體劃分,但仍屬于分代收集器,算法将堆分為若幹個邏輯區域(region),一部分用作年輕代,一部分用作老年代,還有用來存儲巨型對象的分區。

同CMS相同,會周遊所有對象,标記引用情況,清除對象後會對區域進行複制移動,以整合碎片空間。

年輕代回收: 并行複制采用複制算法,并行收集,會StopTheWorld.

老年代回收: 會對年輕代一并回收

初始标記完成堆root對象的标記,會StopTheWorld. 并發标記 GC線程和應用線程并發執行. 最終标記完成三色标記周期,會StopTheWorld. 複制/清楚會優先對可回收空間加大的區域進行回收

b、ZGC算法

前面提供的高效垃圾回收算法,針對大堆記憶體設計,可以處理TB級别的堆,可以做到10ms以下的回收停頓時間。

Java面試題JVM篇
  • 着色指針
  • 讀屏障
  • 并發處理
  • 基于region
  • 記憶體壓縮(整理)

roots标記:标記root對象,會StopTheWorld. 并發标記:利用讀屏障與應用線程一起運作标記,可能會發生StopTheWorld. 清除會清理标記為不可用的對象. roots重定位:是對存活的對象進行移動,以騰出大塊記憶體空間,減少碎片産生.重定位最開始會StopTheWorld,卻決于重定位集與對象總活動集的比例. 并發重定位與并發标記類似。

4、簡述一下JVM的記憶體模型

1.JVM記憶體模型簡介

JVM定義了不同運作時資料區,他們是用來執行應用程式的。某些區域随着JVM啟動及銷毀,另外一

些區域的資料是線程性獨立的,随着線程建立和銷毀。jvm記憶體模型總體架構圖如下:(摘自oracle官方網站)

Java面試題JVM篇

JVM在執行Java程式時,會把它管理的記憶體劃分為若幹個的區域,每個區域都有自己的用途和建立銷毀時間。如下圖所示,可以分為兩大部分,線程私有區和共享區。下圖是根據自己了解畫的一個JVM記憶體模型架構圖:

Java面試題JVM篇

JVM記憶體分為線程私有區和線程共享區

線程私有區

1、程式計數器

當同時進行的線程數超過CPU數或其核心數時,就要通過時間片輪詢分派CPU的時間資源,不免發生線程切換。這時,每個線程就需要一個屬于自己的計數器來記錄下一條要運作的指令。如果執行的是JAVA方法,計數器記錄正在執行的java位元組碼位址,如果執行的是native方法,則計數器為空。

2、虛拟機棧

線程私有的,與線程在同一時間建立。管理JAVA方法執行的記憶體模型。每個方法執行時都會建立一個桢棧來存儲方法的的變量表、操作數棧、動态連結方法、傳回值、傳回位址等資訊。棧的大小決定了方法調用的可達深度(遞歸多少層次,或嵌套調用多少層其他方法,-Xss參數可以設定虛拟機棧大小)。棧的大小可以是固定的,或者是動态擴充的。如果請求的棧深度大于最大可用深度,則抛出stackOverflflowError;如果棧是可動态擴充的,但沒有記憶體空間支援擴充,則抛出OutofMemoryError。 使用jclasslib工具可以檢視class類檔案的結構。下圖為棧幀結構圖:

Java面試題JVM篇

3、本地方法棧

與虛拟機棧作用相似。但它不是為Java方法服務的,而是本地方法(C語言)。由于規範對這塊沒有

強制要求,不同虛拟機實作方法不同。

線程共享區

1、方法區

線程共享的,用于存放被虛拟機加載的類的中繼資料資訊,如常量、靜态變量和即時編譯器編譯後的代碼。若要分代,算是永久代(老年代),以前類大多“static”的,很少被解除安裝或收集,現回收廢棄常量和無用的類。其中運作時常量池存放編譯生成的各種常量。(如果hotspot虛拟機确定一個類的定義資訊不會被使用,也會将其回收。回收的基本條件至少有:所有該類的執行個體被回收,而且裝載該類的ClassLoader被回收)

2、堆

存放對象執行個體和數組,是垃圾回收的主要區域,分為新生代和老年代。剛建立的對象在新生代的Eden區中,經過GC後進入新生代的S0區中,再經過GC進入新生代的S1區中,15次GC後仍存在就進入老年代。這是按照一種回收機制進行劃分的,不是固定的。若堆的空間不夠執行個體配置設定,則OutOfMemoryError。

Java面試題JVM篇

Young Generation:即圖中的Eden + From Space(s0) + To Space(s1)

Eden:存放新生的對象

Survivor Space:有兩個,存放每次垃圾回收後存活的對象(s0+s1)

Old Generation Tenured Generation:即圖中的Old Space,主要存放應用程式中生命周期長的存活對象。

5、說說堆和棧的差別

棧是運作時機關,代表着邏輯,内含基本資料類型和堆中對象引用,所在區域連續,沒有碎片;堆是存儲機關,代表着資料,可被多個棧共享(包括成員中基本資料類型、引用和引用對象),所在區域不連續,會有碎片。

1、功能不同

棧記憶體用來存儲局部變量和方法調用,而堆記憶體用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆記憶體中。

2、共享性不同

棧記憶體是線程私有的。 堆記憶體是所有線程共有的。

3、異常錯誤不同

如果棧記憶體或者堆記憶體不足都會抛出異常。 棧空間不足:java.lang.StackOverFlowError。 堆空間不足:java.lang.OutOfMemoryError。

4、空間大小

棧的空間大小遠遠小于堆的。

6、 什麼時候會觸發FullGC

除直接調用System.gc外,觸發Full GC執行的情況有如下四種。

1. 舊生代空間不足

舊生代空間隻有在新生代對象轉入及建立為大對象、大數組時才會出現不足的現象,當執行Full GC後空間仍然不足,則抛出如下錯誤: java.lang.OutOfMemoryError: Java heap space 為避免以上兩種狀況引起的FullGC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。

2. Permanet Generation空間滿

PermanetGeneration中存放的為一些class的資訊等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會抛出如下錯誤資訊: java.lang.OutOfMemoryError: PermGen space 為避免Perm Gen占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。

3. CMS GC時出現promotion failed和concurrent mode failure

對于采用CMS進行舊生代GC的程式而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。 promotionfailed是在進行Minor GC時,survivor space放不下、對象隻能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。 應對措施為:增大survivorspace、舊生代空間或調低觸發并發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由于JDK的bug29導緻CMS在remark完畢後很久才觸發sweeping動作。對于這種狀況,可通過設定-XX:CMSMaxAbortablePrecleanTime=5(機關為ms)來避免。

4. 統計得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩餘空間

這是一個較為複雜的觸發情況,Hotspot為了避免由于新生代對象晉升到舊生代導緻舊生代空間不足的現象,在進行MinorGC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩餘空間,那麼就直接觸發Full GC。

例如程式第一次觸發MinorGC後,有6MB的對象晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大于6MB,如果小于6MB,則執行Full GC。 當新生代采用PSGC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大于6MB,如小于,則觸發對舊生代的回收。 除了以上4種狀況外,對于使用RMI來進行RPC或管理的Sun JDK應用而言,預設情況下會一小時執行一次Full GC。可通過在啟動時通過- java-Dsun.rmi.dgc.client.gcInterval=3600000來設定Full GC執行的間隔時間或通過-XX:+DisableExplicitGC來禁止RMI調用System.gc。

7、什麼是Java虛拟機?為什麼Java被稱作是“平台無關的程式設計語言”?

Java虛拟機是一個可以執行Java位元組碼的虛拟機程序。Java源檔案被編譯成能被Java虛拟機執行的位元組碼檔案。 Java被設計成允許應用程式可以運作在任意的平台,而不需要程式員為每一個平台單獨重寫或者是重新編譯。Java虛拟機讓這個變為可能,因為它知道底層硬體平台的指令長度和其他特性。

8、Java記憶體結構

Java面試題JVM篇

方法區和對是所有線程共享的記憶體區域;而java棧、本地方法棧和程式員計數器是運作是線程私有的記憶體區域。

  • Java堆(Heap),是Java虛拟機所管理的記憶體中最大的一塊。Java堆是被所有線程共享的一塊記憶體區域,在虛拟機啟動時建立。此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在這裡配置設定記憶體。
  • 方法區(Method Area),方法區(Method Area)與Java堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。
  • 程式計數器(Program Counter Register),程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是目前線程所執行的位元組碼的行号訓示器。
  • JVM棧(JVM Stacks),與程式計數器一樣,Java虛拟機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛拟機棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動态連結、方法出口等資訊。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛拟機棧中從入棧到出棧的過程。
  • 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛拟機棧所發揮的作用是非常相似的,其差別不過是虛拟機棧為虛拟機執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛拟機使用到的Native方法服務。

9、說說對象配置設定規則

  • 對象優先配置設定在Eden區,如果Eden區沒有足夠的空間時,虛拟機執行一次Minor GC。
  • 大對象直接進入老年代(大對象是指需要大量連續記憶體空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的記憶體拷貝(新生代采用複制算法收集記憶體)。
  • 長期存活的對象進入老年代。虛拟機為每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那麼對象會進入Survivor區,之後每經過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。
  • 動态判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代。
  • 空間配置設定擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大于老年區的剩餘值大小則進行一次Full GC,如果小于檢查HandlePromotionFailure設定,如果true則隻進行Monitor GC,如果false則進行Full GC。

10、描述一下JVM加載class檔案的原理機制?

JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實作的,Java中的類加載器是一個重要的Java運作時系統元件,它負責在運作時查找和裝入類檔案中的類。

由于Java的跨平台性,經過編譯的Java源程式并不是一個可執行程式,而是一個或多個類檔案。當Java程式需要使用某個類時,JVM會確定這個類已經被加載、連接配接(驗證、準備和解析)和初始化。類的加載是指把類的.class檔案中的資料讀入到記憶體中,通常是建立一個位元組數組讀入.class檔案,然後産生與所加載類對應的Class對象。加載完成後,Class對象還不完整,是以此時的類還不可用。

當類被加載後就進入連接配接階段,這一階段包括驗證、準備(為靜态變量配置設定記憶體并設定預設的初始值)和解析(将符号引用替換為直接引用)三個步驟。最後JVM對類進行初始化,包括:1)如果類存在直接的父類并且這個類還沒有被初始化,那麼就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。

類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴充加載器(Extension)、系統加載器(System)和使用者自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程采取了父親委托機制(PDM)。PDM更好的保證了Java平台的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載。JVM不會向Java程式提供對Bootstrap的引用。下面是關于幾個類加載器的說明:

  • Bootstrap:一般用本地代碼實作,負責加載JVM基礎核心類庫(rt.jar);
  • Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
  • System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是使用者自定義加載器的預設父加載器。

11、說說Java對象建立過程

1.JVM遇到一條建立對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符号引用。然後加載這個類(類加載過程在後邊講);

2.為對象配置設定記憶體。一種辦法“指針碰撞”、一種辦法“空閑清單”,最終常用的辦法“本地線程緩沖配置設定(TLAB)”;

3.将除對象頭外的對象記憶體空間初始化為0;

4.對對象頭進行必要設定。

12、知道類的生命周期嗎?

類的生命周期包括這幾個部分,加載、連接配接、初始化、使用和解除安裝,其中前三部是類的加載的過程,如下圖;

Java面試題JVM篇
  • 加載,查找并加載類的二進制資料,在Java堆中也建立一個java.lang.Class類的對象;
  • 連接配接,連接配接又包含三塊内容:驗證、準備、初始化。 1)驗證,檔案格式、中繼資料、位元組碼、符号引用驗證; 2)準備,為類的靜态變量配置設定記憶體,并将其初始化為預設值; 3)解析,把類中的符号引用轉換為直接引用;
  • 初始化,為類的靜态變量賦予正确的初始值;
  • 使用,new出對象程式中使用;
  • 解除安裝,執行垃圾回收。

13、簡述Java的對象結構

Java對象由三個部分組成:對象頭、執行個體資料、對齊填充。

對象頭由兩部分組成,第一部分存儲對象自身的運作時資料:哈希碼、GC分代年齡、鎖辨別狀态、線程持有的鎖、偏向線程ID(一般占32/64 bit)。第二部分是指針類型,指向對象的類中繼資料類型(即對象代表哪個類)。如果是數組對象,則對象頭中還有一部分用來記錄數組長度。

執行個體資料用來存儲對象真正的有效資訊(包括父類繼承下來的和自己定義的)

對齊填充:JVM要求對象起始位址必須是8位元組的整數倍(8位元組對齊)

14、如何判斷對象可以被回收?

判斷對象是否存活一般有兩種方式:

引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象互相循環引用的問題。

可達性分析(Reachability Analysis):從GC Roots開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到GC Roots沒有任何引用鍊相連時,則證明此對象是不可用的,不可達對象。

15、JVM的永久代中會發生垃圾回收麼?

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(FullGC)。如果你仔細檢視垃圾收集器的輸出資訊,就會發現永久代也是被回收的。這就是為什麼正确的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到中繼資料區 (注:Java8中已經移除了永久代,新加了一個叫做中繼資料區的native記憶體區)

16、你知道哪些垃圾收集算法

GC最基礎的算法有三種:

标記 -清除算法、複制算法、标記-壓縮算法,我們常用的垃圾回收器一般都采用分代收集算法。

  • 标記 -清除算法,“标記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“标記”和“清除”兩個階段:首先标記出所有需要回收的對象,在标記完成後統一回收掉所有被标記的對象。
  • 複制算法,“複制”(Copying)的收集算法,它将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。
  • 标記-壓縮算法,标記過程仍然與“标記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體
  • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最适當的收集算法。

17、調優指令有哪些?

Sun JDK監控和故障處理指令有jps jstat jmap jhat jstack jinfo。

  • jps,JVM Process Status Tool,顯示指定系統内所有的HotSpot虛拟機程序。
  • jstat,JVM statistics Monitoring是用于監視虛拟機運作時狀态資訊的指令,它可以顯示出虛拟機程序中的類裝載、記憶體、垃圾收集、JIT編譯等運作資料。
  • jmap,JVM Memory Map指令用于生成heap dump檔案;
  • jhat,JVM Heap Analysis Tool指令是與jmap搭配使用,用來分析jmap生成的dump,jhat内置了一個微型的HTTP/HTML伺服器,生成dump的分析結果後,可以在浏覽器中檢視;
  • jstack,用于生成java虛拟機目前時刻的線程快照。
  • jinfo,JVM Confifiguration info 這個指令作用是實時檢視和調整虛拟機運作參數。

18、常見調優工具有哪些

常用調優工具分為兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory

Analyzer Tool)、GChisto。

  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制台,用于對JVM中記憶體,線程和類等的監控
  • jvisualvm,jdk自帶全能工具,可以分析記憶體快照、線程快照;監控記憶體變化、GC變化等。
  • MAT,Memory Analyzer Tool,一個基于Eclipse的記憶體分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找記憶體洩漏和減少記憶體消耗
  • GChisto,一款專業分析gc日志的工具。

19、Minor GC與Full GC分别在什麼時候發生?

新生代記憶體不夠用時候發生MGC也叫YGC,JVM記憶體不夠的時候發生FGC。

20、你知道哪些JVM性能調優參數?(簡單版回答)

  • 設定堆記憶體大小

-Xmx:堆記憶體最大限制。

  • 設定新生代大小。 新生代不宜太小,否則會有大量對象湧入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸園空間和幸存者空間的占比

  • 設定垃圾回收器 年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC。

21、 對象一定配置設定在堆中嗎?有沒有了解逃逸分析技術?

「對象一定配置設定在堆中嗎?」 不一定的,JVM通過「逃逸分析」,那些逃不出方法的對象會在棧上配置設定。

  • 「什麼是逃逸分析?」

逃逸分析(Escape Analysis),是一種可以有效減少Java 程式中同步負載和記憶體堆配置設定壓力的跨函數全局資料流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用範圍,進而決定是否要将這個對象配置設定到堆上。

  • 逃逸分析是指分析指針動态範圍的方法,它同編譯器優化原理的指針分析和外形分析相關聯。當變量(或者對象)在方法中配置設定後,其指針有可能被傳回或者被全局引用,這樣就會被其他方法或者線程所引用,這種現象稱作指針(或者引用)的逃逸(Escape)。通俗點講,如果一個對象的指針被多個方法或者線程引用時,那麼我們就稱這個對象的指針發生了逃逸。

「逃逸分析的好處」

  • 棧上配置設定,可以降低垃圾收集器運作的頻率。
  • 同步消除,如果發現某個對象隻能從一個線程可通路,那麼在這個對象上的操作可以不需要同步。
  • 标量替換,把對象分解成一個個基本類型,并且記憶體配置設定不再是配置設定在堆上,而是配置設定在棧上。這樣的好處有,一、減少記憶體使用,因為不用生成對象頭。二、程式記憶體回收效率高,并且GC頻率也會減少。

22、虛拟機為什麼使用元空間替換了永久代?

「什麼是元空間?什麼是永久代?為什麼用元空間代替永久代?」 我們先回顧一下「方法區」吧,看看虛拟機運作時資料記憶體圖,如下:

Java面試題JVM篇

方法區和堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯後的代碼等資料。

「什麼是永久代?它和方法區有什麼關系呢?」

如果在HotSpot虛拟機上開發、部署,很多程式員都把方法區稱作永久代。可以說方法區是規範,永久代是Hotspot針對該規範進行的實作。在Java7及以前的版本,方法區都是永久代實作的。

「什麼是元空間?它和方法區有什麼關系呢?」

對于Java8,HotSpots取消了永久代,取而代之的是元空間(Metaspace)。換句話說,就是方法區還是在的,隻是實作變了,從永久代變為元空間了。

「為什麼使用元空間替換了永久代?」

永久代的方法區,和堆使用的實體記憶體是連續的。

Java面試題JVM篇

「永久代」是通過以下這兩個參數配置大小的~

  • -XX:PremSize:設定永久代的初始大小
  • -XX:MaxPermSize: 設定永久代的最大值,預設是64M

對于「永久代」,如果動态生成很多class的話,就很可能出現「java.lang.OutOfMemoryError:PermGen space錯誤」,因為永久代空間配置有限嘛。最典型的場景是,在web開發比較多jsp頁面的時候。

  • JDK8之後,方法區存在于元空間(Metaspace)。實體記憶體不再與堆連續,而是直接存在于本地記憶體中,理論上機器「記憶體有多大,元空間就有多大」。
Java面試題JVM篇

可以通過以下的參數來設定元空間的大小:

  • -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就适當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,适當提高該值。
  • -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。
  • -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,

減少為配置設定空間所導緻的垃圾收集

  • -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,

減少為釋放空間所導緻的垃圾收集

「是以,為什麼使用元空間替換永久代?」

表面上看是為了避免OOM異常。因為通常使用PermSize和MaxPermSize設定永久代的大小就決定了永久代的上限,但是不是總能知道應該設定為多大合适, 如果使用預設值很容易遇到OOM錯誤。當使用元空間時,可以加載多少類的中繼資料就不再由MaxPermSize控制, 而由系統的實際可用空間來控制啦。

23、什麼是Stop The World ? 什麼是OopMap?什麼是安全點?

進行垃圾回收的過程中,會涉及對象的移動。為了保證對象引用更新的正确性,必須暫停所有的使用者線程,像這樣的停頓,虛拟機設計者形象描述為「Stop The World」。也簡稱為STW。

在HotSpot中,有個資料結構(映射表)稱為「OopMap」。一旦類加載動作完成的時候,HotSpot就會把對象内什麼偏移量上是什麼類型的資料計算出來,記錄到OopMap。在即時編譯過程中,也會在「特定的位置」生成 OopMap,記錄下棧上和寄存器裡哪些位置是引用。

這些特定的位置主要在:

  • 1.循環的末尾(非 counted 循環)
  • 2.方法臨傳回前 / 調用方法的call指令後
  • 3.可能抛異常的位置

這些位置就叫作「安全點(safepoint)。」 使用者程式執行時并非在代碼指令流的任意位置都能夠在停頓下來開始垃圾收集,而是必須是執行到安全點才能夠暫停。

24、說一下JVM 的主要組成部分及其作用?

Java面試題JVM篇

JVM包含兩個子系統和兩個元件,分别為

  • Class loader(類裝載子系統)
  • Execution engine(執行引擎子系統);
  • Runtime data area(運作時資料區元件)
  • Native Interface(本地接口元件)。
  • 「Class loader(類裝載):」 根據給定的全限定名類名(如:java.lang.Object)來裝載class檔案到運作時資料區的方法區中。
  • 「Execution engine(執行引擎)」:執行class的指令。
  • 「Native Interface(本地接口):」 與native lib互動,是其它程式設計語言互動的接口。
  • 「Runtime data area(運作時資料區域)」:即我們常說的JVM的記憶體。

首先通過編譯器把 Java源代碼轉換成位元組碼,Class loader(類裝載)再把位元組碼加載到記憶體中,将其放在運作時資料區的方法區内,而位元組碼檔案隻是 JVM 的一套指令集規範,并不能直接交給底層作業系統去執行,是以需要特定的指令解析器執行引擎(Execution Engine),将位元組碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實作整個程式的功能。

25、什麼是指針碰撞?

一般情況下,JVM的對象都放在堆記憶體中(發生逃逸分析除外)。當類加載檢查通過後,Java虛拟機開始為新生對象配置設定記憶體。如果Java堆中記憶體是絕對規整的,所有被使用過的的記憶體都被放到一邊,空閑的記憶體放到另外一邊,中間放着一個指針作為分界點的訓示器,所配置設定記憶體僅僅是把那個指針向空閑空間方向挪動一段與對象大小相等的執行個體,這種配置設定方式就是 指針碰撞。

Java面試題JVM篇

26,什麼是空閑清單?

如果Java堆記憶體中的記憶體并不是規整的,已被使用的記憶體和空閑的記憶體互相交錯在一起,不可以進行指針碰撞啦,虛拟機必須維護一個清單,記錄哪些記憶體是可用的,在配置設定的時候從清單找到一塊大的空間配置設定給對象執行個體,并更新清單上的記錄,這種配置設定方式就是空閑清單。

27,什麼是TLAB?

可以把記憶體配置設定的動作按照線程劃分在不同的空間之中進行,每個線程在Java堆中預先配置設定一小塊記憶體,這就是TLAB(Thread Local Allocation Buffffer,本地線程配置設定緩存)。虛拟機通過 -XX:UseTLAB 設定它的。

28、對象頭具體都包含哪些内容?

在我們常用的Hotspot虛拟機中,對象在記憶體中布局實際包含3個部分:

1. 對象頭、2. 執行個體資料、3. 對齊填充

而對象頭包含兩部分内容,Mark Word中的内容會随着鎖标志位而發生變化,是以隻說存儲結構就好了。

1. 對象自身運作時所需的資料,也被稱為Mark Word,也就是用于輕量級鎖和偏向鎖的關鍵點。具體的内容包含對象的hashcode、分代年齡、輕量級鎖指針、重量級鎖指針、GC标記、偏向鎖線程ID、偏向鎖時間戳。

2. 存儲類型指針,也就是指向類的中繼資料的指針,通過這個指針才能确定對象是屬于哪個類的執行個體。

如果是數組的話,則還包含了數組的長度。

Java面試題JVM篇

29、你知道哪些JVM調優參數?

「堆棧記憶體相關」

  • -Xms 設定初始堆的大小
  • -Xmx 設定最大堆的大小
  • -Xmn 設定年輕代大小,相當于同時配置-XX:NewSize和-XX:MaxNewSize為一樣的值
  • -Xss 每個線程的堆棧大小
  • -XX:NewSize 設定年輕代大小(for 1.3/1.4)
  • -XX:MaxNewSize 年輕代最大值(for 1.3/1.4)
  • -XX:NewRatio 年輕代與年老代的比值(除去持久代)
  • -XX:SurvivorRatio Eden區與Survivor區的的比值
  • -XX:PretenureSizeThreshold 當建立的對象超過指定大小時,直接把對象配置設定在老年代。
  • -XX:MaxTenuringThreshold設定對象在Survivor複制的最大年齡門檻值,超過門檻值轉移到

老年代

「垃圾收集器相關」

  • -XX:+UseParallelGC:選擇垃圾收集器為并行收集器。
  • -XX:ParallelGCThreads=20:配置并行收集器的線程數
  • -XX:+UseConcMarkSweepGC:設定年老代為并發收集。
  • -XX:CMSFullGCsBeforeCompaction=5 由于并發收集器不對記憶體空間進行壓縮、整理,

是以運作一段時間以後會産生“碎片”,使得運作效率降低。此值設定運作5次GC以後對内

存空間進行壓縮、整理。

  • -XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,但是

可以消除碎片

「輔助資訊相關」

  • -XX:+PrintGCDetails 列印GC詳細資訊
  • -XX:+HeapDumpOnOutOfMemoryError讓JVM在發生記憶體溢出的時候自動生成記憶體快照,

排查問題用

  • -XX:+DisableExplicitGC禁止系統System.gc(),防止手動誤觸發FGC造成問題.
  • -XX:+PrintTLAB 檢視TLAB空間的使用情況

30、說一下 JVM 有哪些垃圾回收器?

如果說垃圾收集算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實作。下圖展示了7種作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、ParallelScavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。

Java面試題JVM篇
  • Serial收集器(複制算法): 新生代單線程收集器,标記和清理都是單線程,優點是簡單高效;
  • ParNew收集器 (複制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;
  • Parallel Scavenge收集器 (複制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 使用者線程時間/(使用者線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程式的運算任務,适合背景應用等對互動相應要求不高的場景;
  • Serial Old收集器 (标記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标記-整理算法):老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标記-清除算法):老年代并行收集器,以擷取最短回收停頓時間為目标的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
  • G1(Garbage First)收集器 (标記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“标記-整理”算法實作,也就是說不會産生記憶體碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限于新生代或老年代。
  • ZGC (Z Garbage Collector)是一款由Oracle公司研發的,以低延遲為首要目标的一款垃圾收集器。它是基于動态Region記憶體布局,(暫時)不設年齡分代,使用了讀屏障、染色指針和記憶體多重映射等技術來實作可并發的标記-整理算法的收集器。在 JDK 11 新加入,還在實驗階段,主要特點是:回收TB級記憶體(最大4T),停頓時間不超過10ms。優點:低停頓,高吞吐量, ZGC 收集過程中額外耗費的記憶體小。缺點:浮動垃圾目前使用的非常少,真正普及還是需要寫時間的。

新生代收集器:Serial、 ParNew 、 Parallel Scavenge

老年代收集器: CMS 、Serial Old、Parallel Old

整堆收集器: G1 , ZGC (因為不涉年代不在圖中)。

繼續閱讀