JVM 常見面試題指南
阿新 • • 發佈:2021-12-16
基礎
1. JDK、JRE、JVM的關係是什麼?
什麼是 JVM?
英文名稱 (Java Virtual Machine ),就是JAVA 虛擬機器,它只識別 .class 型別檔案,它
能夠將 class 檔案中的位元組碼指令進行識別並呼叫作業系統向上的 API 完成動作。
什麼是 JRE?
英文名稱 ( Java Runtime Environment ), Java 執行時環境。
它主要包含兩個部分:
JVM 的標準實現和 Java 的一些基本類庫。
相對於 JVM 來說,JRE多出來一部分Java 類庫。
什麼是 JDK?
英文名稱 ( Java Development Kit ),Java 開發工具包。 JDK是整個Java 開發的核心,它集成了 JRE和一些好用的小工具。 例如:javac.exe、java.exe、jar.exe 等。 這三者的關係:一層層的巢狀關係。 JDK > JRE > JVM。
2. JVM 的記憶體模型以及分割槽情況和作用
如下圖所示:
黃色部分為執行緒共有,藍色部分為執行緒私有。
方法區
用於儲存虛擬機器載入的類資訊,常量,靜態變數等資料。
堆
存放物件例項,所有的物件和陣列都要在堆上分配,是 JVM 所管理的記憶體中最大的一塊區域。
棧
Java 方法執行的記憶體模型:
儲存區域性變量表,運算元棧,動態連結,方法出口等資訊,生命週期與執行緒相同。
本地方法棧
作用與虛擬機器棧類似。
不同點
本地方法棧為 native 方法執行服務
虛擬機器棧為虛擬機器執行的 Java 方法服務。
程式計數器
當前執行緒所執行的行號指示器,是 JVM 記憶體區域最小的一塊區域,執行位元組碼工作時就是利用程式計數器來選取下一條需要執行的位元組碼指令。
3. JVM物件建立步驟流程是什麼?
整體流程如下圖所示:
- 第1步:虛擬機器遇到一個 new 指令,首先將去檢查這個指令的引數是否能在常量池中定位到這個類的符號引用,並且檢查這個符號引用的類是否已經被 載入 - 解析 - 初始化。
- 第2步:如果類已經被載入那麼進行第3步;如果沒有進行載入,那麼就需要先進行類的載入。
- 第3步:類載入檢查通過之後,接下來進行新生物件的記憶體分配。
- 第4步:物件生成需要的記憶體大小在類載入完成後便可完全確定,為物件分配空間等同於把一塊確定大小的記憶體從 Java堆 中劃分出來。
- 第5步:記憶體大小的劃分分為兩種情況:
- 第一種情況:JVM的記憶體是規整的,所有的使用的記憶體都放到一邊,空閒的記憶體在另外一邊,中間放一個指標作為分界點的指示器。那麼這時候分配記憶體就比較簡單,只要講指標向空閒空間那邊挪動一段與物件大小相同的距離。這種就是“指標碰撞”。
- 第二種情況:JVM 的記憶體不是規整的,也就是說已使用的記憶體與未使用的記憶體相互交錯。這時候就沒辦法利用指標碰撞了。這時候我們就需要維護一張表,用於記錄那些記憶體可用,在分配的時候從列表中找到一塊足夠大的空間劃分給物件例項,並更新到記錄表上。
- 第6步:空間申請完成之後,JVM需要將記憶體的空間都初始化為 0 值。如果使用 TLAB,就可以在 TLAB 分配的時候就可以進行該工作。
- 第7步:JVM對物件進行必要的設定。例如,這個物件是哪個類的例項、物件的雜湊碼、GC年代等資訊。
- 第8步:完成了上面的步驟之後 從 JVM 來看一個物件基本上完成了,但從 Java 程式程式碼絕對來看,物件建立才剛剛開始,需要執行 < init > 方法,按照程式中設定的初始化操作初始化,這時候一個真正的程式物件生成了。
4. 垃圾回收演算法有幾種型別?他們對應的優缺點又是什麼?
常見的垃圾回收演算法有:
標記-清除演算法
複製演算法
標記-整理演算法
分代收集演算法
標記-清除演算法
標記-清除演算法包括兩個階段:"標記" 和 "清除"。
標記階段:確定所有要回收的物件,並做標記。
清除階段:將標記階段確定不可用的物件清除。
缺點:
1.標記和清除的效率都不高。
2.會產生大量的碎片,而導致頻繁的回收。
複製演算法
記憶體分成大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候, 把存活的物件複製到另一塊上,然後把這塊記憶體整個清理掉。
缺點:
1.需要浪費額外的記憶體作為複製區。
2.當存活率較高時,複製演算法效率會下降。
標記-整理演算法
標記-整理演算法不是把存活物件複製到另一塊記憶體,而是把存活物件往記憶體的一端移動,然後直接回收邊界以外的記憶體。
缺點: 演算法複雜度大,執行步驟較多
分代收集演算法
目前大部分 JVM 的垃圾收集器採用的演算法。
根據物件存活的生命週期將記憶體劃分為若干個不同的區域。
一般情況下將堆區劃分為新生代( Young Generation) 和 老年代( Tenured Generation ),永久代( Permanet Generation )。
老年代的特點是每次垃圾收集時只有少量物件需要被回收,
而新生代的特點是每次垃圾回收時都有大量的物件需要被回收,
那麼就可以根據不同代的特點採取最適合的收集演算法。
Young:存放新建立的物件,物件生命週期非常短,幾乎用完可以立即回收,也叫 Eden 區。
Tenured: Young 區多次回收後存活下來的物件將被移到 Tenured 區,也叫 old 區。
Perm:永久帶,主要存載入的類資訊,生命週期長,幾乎不會被回收。
缺點: 演算法複雜度大,執行步驟較多。
5. 簡單介紹一下什麼是類載入機制?
Class 檔案由類裝載器裝載後,在 JVM 中將形成一份描述 Class 結構的元資訊物件,通過該元資訊物件可以獲知 Class 的結構資訊:如建構函式,屬性和方法等。
虛擬機器把描述類的資料從 Class 檔案載入到記憶體,並對資料進行校驗,轉換解析和初始化,最終形成可以被虛擬機器直接使用的 Java 型別,這就是虛擬機器的類載入機制。
6. 類的載入過程是什麼?簡單描述一下每個步驟
類載入的過程包括了:
五個階段:
載入
驗證
準備
解析
初始化
- 第一步:載入
載入是類載入過程的第一個階段,虛擬機器在這一階段需要完成以下三件事情:- 通過類的全限定名來獲取其定義的二進位制位元組流
- 將位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構
- 在 Java 堆中生成一個代表這個類的 java.lang.Class 物件,作為對方法區中這些資料的訪問入口
- 第二步:驗證
確保被載入的類的正確性。
這一階段是確保 Class 檔案的位元組流中包含的資訊符合當前虛擬機器的規範,並且不會損害虛擬機器自身的安全。
包含了四個驗證動作:檔案格式驗證,元資料驗證,位元組碼驗證,符號引用驗證。 - 第三步:準備
為類的靜態變數分配記憶體,並將其初始化為預設值。
準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區中分配。 - 第四步:解析
把類中的符號引用轉換為直接引用。
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫點限定符 7 類符號引用進行。 - 第五步:初始化
類變數進行初始化。
為類的靜態變數賦予正確的初始值,JVM 負責對類進行初始化,主要對類變數進行初始化。
7. JVM 預定義的類載入器有哪幾種?分別什麼作用?
啟動(Bootstrap)類載入器
標準擴充套件(Extension)類載入器
應用程式類載入器(Application)
啟動(Bootstrap)類載入器
引導類裝入器是用原生代碼實現的類裝入器,它負責將 < JavaRuntimeHome >/lib 下面的類庫載入到記憶體中。
由於引導類載入器涉及到虛擬機器本地實現細節,開發者無法直接獲取到啟動類載入器的引用。
標準擴充套件(Extension)類載入器
擴充套件類載入器負責將 < Java_Runtime_Home >/lib/ext 或者由系統變數 java.ext.dir 指定位置中的類庫載入到記憶體中。
開發者可以直接使用標準擴充套件類載入器。
應用程式類載入器(Application)
應用程式類載入器(Application ClassLoader):負責載入使用者路徑(classpath)上的類庫。
8. 什麼是雙親委派模式?有什麼作用?
基本定義
雙親委派模型的工作流程是:
如果一個類載入器收到了類載入的請求,它首先不會自己去載入這個類,
而是把請求委託給父載入器去完成,依次向上,
因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,
只有當父載入器沒有找到所需的類時,子載入器才會嘗試去載入該類。
雙親委派機制:
1.當 AppClassLoader 載入一個 class 時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器 ExtClassLoader 去完成。
2.當 ExtClassLoader 載入一個 class 時,它首先也不會自己去嘗試載入這個類,而是把類載入請求委派給 BootStrapClassLoader 去完成。
3.如果 BootStrapClassLoader 載入失敗,會使用 ExtClassLoader 來嘗試載入。
4.若 ExtClassLoader 也載入失敗,則會使用 AppClassLoader 來載入,如果 AppClassLoader 也載入失敗,則會報出異常 ClassNotFoundException。
如下圖所示:
雙親委派作用:
通過帶有優先順序的層級關可以避免類的重複載入。
保證 Java 程式安全穩定執行,Java 核心 API 定義型別不會被隨意替換。
8. 介紹一下 JVM 中垃圾收集器有哪些? 他們特點分別是什麼?
新生代垃圾收集器
Serial 收集器
特點:Serial 收集器只能使用一條執行緒進行垃圾收集工作,並且在進行垃圾收集的時候,所有的工作執行緒都需要停止工作,等待垃圾收集執行緒完成以後,其他執行緒才可以繼續工作。
使用演算法:複製演算法
ParNew 收集器
特點:ParNew 垃圾收集器是Serial收集器的多執行緒版本。
為了利用 CPU 多核多執行緒的優勢,ParNew 收集器可以執行多個收集執行緒來進行垃圾收集工作。
這樣可以提高垃圾收集過程的效率。
使用演算法:複製演算法
Parallel Scavenge 收集器
特點:Parallel Scavenge 收集器是一款多執行緒的垃圾收集器,但是它又和 ParNew 有很大的不同點。
Parallel Scavenge 收集器和其他收集器的關注點不同。
其他收集器,比如 ParNew 和 CMS 這些收集器,它們主要關注的是如何縮短垃圾收集的時間。
而 Parallel Scavenge 收集器關注的是如何控制系統執行的吞吐量。
這裡說的吞吐量,指的是 CPU 用於執行應用程式的時間和 CPU 總時間的佔比,吞吐量 = 程式碼執行時間 / (程式碼執行時間 + 垃圾收集時間)。
如果虛擬機器執行的總的 CPU 時間是 100 分鐘,而用於執行垃圾收集的時間為 1 分鐘,那麼吞吐量就是 99%。
使用演算法:複製演算法
老年代垃圾收集器
Serial Old 收集器
特點:Serial Old 收集器是 Serial 收集器的老年代版本。這款收集器主要用於客戶端應用程式中作為老年代的垃圾收集器,也可以作為服務端應用程式的垃圾收集器。
使用演算法:標記-整理
Parallel Old 收集器
特點:Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本這個收集器是在 JDK1.6 版本中出現的,所以在 JDK1.6 之前,新生代的 Parallel Scavenge 只能和 Serial Old 這款單執行緒的老年代收集器配合使用。
Parallel Old 垃圾收集器和 Parallel Scavenge 收集器一樣,也是一款關注吞吐量的垃圾收集器,和 Parallel Scavenge 收集器一起配合,可以實現對 Java 堆記憶體的吞吐量優先的垃圾收集策略。
使用演算法:標記-整理
CMS 收集器
特點:CMS 收集器是目前老年代收集器中比較優秀的垃圾收集器。
CMS 是 Concurrent Mark Sweep,從名字可以看出,這是一款使用 "標記-清除" 演算法的併發收集器。
CMS 垃圾收集器是一款以獲取最短停頓時間為目標的收集器。
如下圖所示:
從圖中可以看出,CMS 收集器的工作過程可以分為 4 個階段:
- 初始標記(CMS initial mark)階段
- 併發標記(CMS concurrent mark)階段
- 重新標記(CMS remark)階段
- 併發清除((CMS concurrent sweep)階段
使用演算法:複製+標記清除
G1 垃圾收集器
特點: 主要步驟:初始標記,併發標記,重新標記,複製清除。
使用演算法:複製 + 標記整理
9. 什麼是 Class 檔案? Class 檔案主要的資訊結構有哪些?
Class 檔案是一組以 8 位位元組為基礎單位的二進位制流。
各個資料項嚴格按順序排列。
Class 檔案格式採用一種類似於 C 語言結構體的偽結構來儲存資料。
這樣的偽結構僅僅有兩種資料型別:
無符號數 和 表。
無符號數:是基本資料型別。
以 u1、u2、u4、u8 分別代表 1 個位元組、2 個位元組、4 個位元組、8 個位元組的無符號數,能夠用來描寫敘述數字、索引引用、數量值或者依照 UTF-8 編碼構成的字串值。
表:由多個無符號數或者其它表作為資料項構成的複合資料型別。
全部表都習慣性地以 _info結尾。
10. 物件“物件已死” 是什麼概念?
物件不可能再被任何途徑使用,稱為物件已死。
判斷物件已死的方法有:引用計數法 與 可達性分析演算法。
迸階
11. Java 語言怎麼實現跨平臺的?
我們編寫的 Java 原始碼,編譯後會生成一種 .class 檔案,稱為位元組碼檔案。
位元組碼不能直接執行,必須通過 JVM 翻譯成機器碼才能執行,JVM是一個”橋樑“,是一個”中介軟體“,是實現跨平臺的關鍵。
Java 程式碼首先被編譯成位元組碼檔案,再由 JVM 將位元組碼檔案翻譯成機器語言,從而達到執行 Java 程式的目的。
12. JVM資料執行區,哪些會造成 OOM 的情況?
除了資料執行區,其他區域均有可能造成 OOM 的情況。
堆溢位:java.lang .OutofMemoryError: Java heap space
棧溢位:java.lang.StackoverflowError
永久代溢位(記憶體溢位):java.lang.outofMemoryError: PermGen space
13. 詳細介紹一下物件在分帶記憶體區域的分配過程?
1.JVM會試圖為相關 Java 物件在Eden 中初始化一塊記憶體區域。
2.當Eden 空間足夠時,記憶體申請結束;否則到下一步。
3.JVM 試圖釋放在Eden 中所有不活躍的物件(這厲於1或更高階的垃圾回收)。釋放後若 Eden 空問仍然不足以放入新物件,則試圖將部分 Eden 中活躍物件放入Survivor 區。
4. Survivor 區被用區作為 Eden 及 old 的中間交換區域,當old區空間足夠時,Survivor區的物件會被移到 old 區,否則會被保留在 Survivor區。
5. 當old 區空間不夠時,JVM 會在 old 區進行完全的垃圾收集。
6.完全垃圾收集後,若 survivor 及 old 區仍然無法存放從Eden 複製過區的部分物件,導致 JVM 無法在 Eden 區為新物件建立記憶體區域,則出現 "out of memory"錯誤。
14. G1 與CMS 兩個垃圾收集器的對比
細節方面不同
- G1 在壓縮空間方面有優勢。
- G1 通過將記憶體空問分成區域 (Region)的方式避免記憶體碎片問題,
- Eden , Survivor , old 區不再固定、在記憶體使用效率上區說更靈活。
- G1 可以通過設定預期停頓時間 (Pause Time) 區控制垃圾收集時間避免應用雪崩現象。
- G1 在回收記憶體後會馬上同時做合併空閒記憶體的工作、而 CMS 預設是在 STW (stopthe world)的時候做
- G1 會在Young GC 中使用、而 CMS 只能在 o 區使用。
整體內容不同
- 吞吐量優先:G1
- 響應優先:CMS
- CMS 的缺點是對 cpu 的要求比較高。
- G1是將記憶體化成了多塊,所有對內段的大小有很大的要求。
- CMS 是清除,所以會存在很多的記憶體碎片。
- G1 是整理,所以碎片空間較小。
15. 線上常用的JVM引數有哪些?
資料區設定
- Xms:初始堆大小
- Xmx:最大堆大小
- Xss: Java 每個執行緒的Stack大小
- XX:NewSize=n:設定年輕代大小。
- XX:NewRatio=n:設定年輕代和年老代的比代。如:為3,表示年輕代與年老代比代為 1:3,年輕代佔整個年輕代年老代和的 1/4。
- XX:SurvivorRatio=n:年輕代中 Eden 區與兩個 Survivor 區的比代。注意 Survivor區有兩個。如:3,表示 Eden : Survivor=3: 2,一個 survivor 區佔整個年輕代的1 / 5。
- XX: MaxPermSize=n:設定持久代大小。
收集器設定
- XX:+UseSesialGC:設定序列收集器
- XX:+UsePasallelGC:設定並行收集器
- XX:+UsePasalledlOld GC:設定並行年老代收集器
- XX:+UseConcMask SweepGC:設定併發收集器
GC日誌列印設定
- XX:+PsintGC:列印GC 的簡要資訊
- XX:+PsintGCDetails:列印GC詳細資訊
- XX:+PsintGCTimeStamps:輸出 GC 的時間戳
16. 物件什麼時候進入老年代?
物件優先在 Eden 區分配記憶體
當物件首次建立時,會放在新生代的 eden 區,若沒有 GC 的介入,會一直在 eden 區, GC後,是可能進入 susvivos 區或者年老代
大物件直接進入老年代
所謂的大物件是指需要大量連續記憶體空間的 Java 物件,
最典型的大物件就是那種很長的字串以及陣列,
大物件對虛擬機器的記憶體分配就是壞訊息,
尤其是一些朝生夕滅的短命大物件,寫程式時應避免。
長期存活的物件進入老年代
所謂的大物件是指需要大量連續記憶體空間的 Java 物件,
最典型的大物件就是那種很長的字串以及陣列,
大物件對虛擬機器的記憶體分配就是壞訊息,
尤其是一些朝生夕滅的短命大物件,寫程式時應避免。
17. 什麼是記憶體溢位,記憶體洩露?他們的區別是什麼?
記憶體溢位 out of memory,
是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現 out of memosy;
記憶體洩露memory leak,
是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆略後果很嚴重,無論多少記憶體,遲早會被佔光。
記憶體溢位就是你要求分配的記憶體超出了系統能給你的,系統不能滿足需求,於是產生溢位。
記憶體洩漏是指你向系統申請分配記憶體進行使用(new),可是使用完了以後卻不歸還(delete),結果你申請到的那塊記憶體你自己也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給需要的程式。
18. 引起類載入操作的操為有哪些?
1. 遇到new、getstatic、putstatic 或 invokestatic 這四條位元組碼指令。
2. 反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3. 子類初始化的時候,如果其父類還沒初始化,則需先觸發其父類的初始化。
4. 虛擬機器執行主類的時候(有main ( stringll args))
5. JDK1.7 動態語言支援。
19. 介紹一下 JVM 提供的常用工貝
- jps:用來顯示本地的Java 程序,可以檢視本地執行著幾個 Java程式,並顯示他們的程序號
命令格式:ips - jinfo:執行環境引數:Java System 屬性和 JVM 命令列引數,Java class path 等資訊
命令格式:info 程序 pid - jstat:監視虛擬機器各種執行狀態資訊的命令列工具
命令格式:jstat -gc 123 250 20 - jstack:可以觀察到 JVM 中當前所有執行緒的執行情況和執行緒當前狀態。
命令格式:istack 程序 pid - jmap:觀察執行中的 JVM 實體記憶體的佔用情況(如:產生哪些物件,及其數量)
命令格式:jmap [loption] pid
20. Full GC、 Major GC、Minor GC 之間區別?
Minor GC:
從新生代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC。
Major GC:
清理 Tenured 區,用於回收老年代,出現Major GC 通現會出現至少一次Minor GC。
Full GC:
Full GC 是針對整個新生代、老年代、元空間 (metaspace, java8 以上版本取代 permgen)的全域性範圍的GC。
21. 什麼時候觸發 Full GC?
- 呼叫 System.gc 時,系統建議執行 Full GC, 但是不必然執行。
- 老年代空間不足。
- 方法區空間不足。
- 超過 Minor GC 後進入老年代的平均大小大於老年代的可用記憶體。
- 由 Eden 區、survivor spacel (From Space)區向 survivor space2 (To Space)區複製時,物件大小大於 To Space 可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小於該物件大小。
22. 什麼情況下會出現棧溢位
- 方法建立了個很大的物件,如 List , Array。
- 是否產生了迴圈呼叫、死迴圈。
- 是否引用了較大的全域性變數。
23. 說一下強引用、軟引用、弱引用、虛引用以及他們之間和 GC 的關係
- 強引用:new 出的物件之類的引用,只要強引用還在,永遠不會回收。
- 軟引用:引用但非必須的物件,記憶體溢位異常之前,回收。
- 弱引用:非必須的物件,物件能生存到下一次垃圾收集發生之前。
- 虛引用:對生存時間無影響,在垃圾回收時得到通知。
24. Eden 和 Survivor 的比例分配是什麼情況?為什麼?
預設比例 8 : 1 。
大部分物件都是朝生夕死。
複製演算法的基本思想就是將記憶體分為兩塊,每次只用其中一塊,當這一塊記憶體用完,就
將還活著的物件複製到另外一塊上面。複製演算法不會產生記憶體碎片。
實戰
25. CPU 資源佔用過高
- top 檢視當前 CPU 情況,找到佔用 CPU 過高的程序 PID=123。
- top -H -p123 找出兩個 CPU 佔用較高的執行緒,記錄下來 PID=2345, 3456轉換為十六進位制。
- jstack -l 123 > temp.txt 打印出當前程序的執行緒棧。
- 查詢到對應於第二步的兩個執行緒執行棧,分析程式碼。
26. 00M 異常排查
- 使用top指令查詢伺服器系統狀態。
- ps -aux|grep java 找出當前 Java 程序的 PID。
- jmap -histo:dve pid 可用統計存活物件的分佈情況,從高到低檢視佔據記憶體最多的物件。
- jmap -dump:format=b,fice=檔名 [pid] 利用 Jmap dump。
- 使用效能分析工具對上一步dump出來的檔案進行分析,工具有 MAT 等。