基礎
1.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。
1.2 JVM 的內存模型以及分區情況和作用
如下圖所示:
[圖片上傳失敗...(image-2df7bd-1618237334080)]
黃色部分為線程共有,藍色部分為線程私有。
方法區
用于存儲虛擬機加載的類信息,常量,靜態變量等數據。
堆
存放對象實例,所有的對象和數組都要在堆上分配。 是 JVM 所管理的內存中最大的一塊區域。
棧
Java 方法執行的內存模型:存儲局部變量表,操作數棧,動態鏈接,方法出口等信息。生命周期與線程相同。
本地方法棧
作用與虛擬機棧類似,不同點本地方法棧為 native 方法執行服務,虛擬機棧為虛擬機執行的 Java 方法服務。
程序計數器
當前線程所執行的行號指示器。是 JVM 內存區域最小的一塊區域。執行字節碼工作時就是利用程序計數器來選取下一條需要執行的字節碼指令。
1.3 JVM 對象創建步驟流程是什么?
整體流程如下圖所示:
[圖片上傳失敗...(image-e72caa-1618237334080)]
第 1 步:虛擬機遇到一個 new 指令,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用, 并且檢查這個符號引用的類是否已經被加載&解析&初始化。
第 2 步:如果類已經被加載那么進行第 3 步; 如果沒有進行加載, 那么就就需要先進行類的加載。
第 3 步:類加載檢查通過之后, 接下來進行新生對象的內存分配。
第 4 步:對象生成需要的內存大小在類加載完成后便可完全確定,為對象分配空間等同于把一塊確定大小的內存從 Java 堆中劃分出來
第 5 步:內存大小的劃分分為兩種情況: 第一種情況:JVM 的內存是規整的, 所有的使用的內存都放到一邊, 空閑的內存在另外一邊, 中間放一個指針作為分界點的指示器。 那么這時候分配內存就比較簡單, 只要講指針向空閑空間那邊挪動一段與對象大小相同的距離。 這種就是“指針碰撞”。
第二種情況:JVM 的內存不是規整的, 也就是說已使用的內存與未使用的內存相互交錯。 這時候就沒辦法利用指正碰撞了。 這時候我們就需要維護一張表,用于記錄那些內存可用, 在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例, 并更新到記錄表上。
第 6 步:空間申請完成之后, JVM 需要將內存的空間都初始化為 0 值。如果使用 TLAB, 就可以在 TLAB 分配的時候就可以進行該工作。
第 7 步: JVM 對對象進行必要的設置。 例如, 這個對象是哪個類的實例、對象的哈希碼、GC 年代等信息。
第 8 步:完成了上面的步驟之后 從 JVM 來看一個對象基本上完成了, 但從 Java 程序代碼絕對來看, 對象創建才剛剛開始, 需要執行 < init > 方法, 按照程序中設定的初始化操作初始化, 這時候一個真正的程序對象生成了。
1.4 垃圾回收算法有幾種類型? 他們對應的優缺點又是什么?
常見的垃圾回收算法有:
標記-清除算法、復制算法、標記-整理算法、分代收集算法
標記-清除算法
標記—清除算法包括兩個階段:“標記”和“清除”。 標記階段:確定所有要回收的對象,并做標記。 清除階段:將標記階段確定不可用的對象清除。
缺點:
標記和清除的效率都不高。
會產生大量的碎片,而導致頻繁的回收。
復制算法
內存分成大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候, 把存活的對象復制到另一塊上,然后把這塊內存整個清理掉。
缺點:
需要浪費額外的內存作為復制區。
當存活率較高時,復制算法效率會下降。
標記-整理算法
標記—整理算法不是把存活對象復制到另一塊內存,而是把存活對象往內存的一端移動,然后直接回收邊界以外的內存。
缺點: 算法復雜度大,執行步驟較多
分代收集算法
目前大部分 JVM 的垃圾收集器采用的算法。根據對象存活的生命周期將內存劃分為若干個不同的區域。一般情況下將堆區劃分為新生代( Young Generation 和老年代( Tenured Generation ),永久代( Permanet Generation )。
老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據不同代的特點采取最適合的收集算法。
如下圖所示:
[圖片上傳失敗...(image-409231-1618237334080)]
Young:存放新創建的對象,對象生命周期非常短,幾乎用完可以立即回收,也叫 Eden 區。
Tenured: young 區多次回收后存活下來的對象將被移到 tenured 區,也叫 old 區。
Perm:永久帶,主要存加載的類信息,生命周期長,幾乎不會被回收。
缺點: 算法復雜度大,執行步驟較多。
1.5 簡單介紹一下什么是類加載機制?
Class 文件由類裝載器裝載后,在 JVM 中將形成一份描述 Class 結構的元信息對象,通過該元信息對象可以獲知 Class 的結構信息:如構造函數,屬性和方法等。
虛擬機把描述類的數據從 class 文件加載到內存,并對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。
1.6 類的加載過程是什么?簡單描述一下每個步驟
類加載的過程包括了:
加載、驗證、準備、解析、初始化五個階段
第一步:加載
查找并加載類的二進制數據。
加載是類加載過程的第一個階段,虛擬機在這一階段需要完成以下三件事情:
通過類的全限定名來獲取其定義的二進制字節流
將字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
在 Java 堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口
第二步:驗證
確保被加載的類的正確性。
這一階段是確保 Class 文件的字節流中包含的信息符合當前虛擬機的規范,并且不會損害虛擬機自身的安全。包含了四個驗證動作:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
第三步:準備
為類的靜態變量分配內存,并將其初始化為默認值。
準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些內存都將在方法區中分配。
第四步:解析
把類中的符號引用轉換為直接引用。
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符 7 類符號引用進行。
第五步:初始化
類變量進行初始化
為類的靜態變量賦予正確的初始值,JVM 負責對類進行初始化,主要對類變量進行初始化。
1.7 JVM 預定義的類加載器有哪幾種?分別什么作用?
啟動(Bootstrap)類加載器、標準擴展(Extension)類加載器、應用程序類加載器(Application)
啟動(Bootstrap)類加載器
引導類裝入器是用本地代碼實現的類裝入器,它負責將 < JavaRuntimeHome >/lib 下面的類庫加載到內存中。由于引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類加載器的引用。
標準擴展(Extension)類加載器
擴展類加載器負責將 < Java_Runtime_Home >/lib/ext 或者由系統變量 java.ext.dir 指定位置中的類庫加載到內存中。開發者可以直接使用標準擴展類加載器。
應用程序類加載器(Application)
應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。
1.8 什么是雙親委派模式?有什么作用?
基本定義: 雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把請求委托給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器沒有找到所需的類時,子加載器才會嘗試去加載該類。
雙親委派機制:
當 AppClassLoader 加載一個 class 時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader 去完成。 當 ExtClassLoader 加載一個 class 時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader 去完成。如果 BootStrapClassLoader 加載失敗,會使用 ExtClassLoader 來嘗試加載; 若 ExtClassLoader 也加載失敗,則會使用 AppClassLoader 來加載,如果 AppClassLoader也加載失敗,則會報出異常 ClassNotFoundException。
如下圖所示:
[圖片上傳失敗...(image-a133b6-1618237334080)]
雙親委派作用:
通過帶有優先級的層級關可以避免類的重復加載;
保證 Java 程序安全穩定運行,Java 核心 API 定義類型不會被隨意替換。
1.9 介紹一下 JVM 中垃圾收集器有哪些? 他們特點分別是什么?
新生代垃圾收集器
Serial 收集器
特點: Serial 收集器只能使用一條線程進行垃圾收集工作,并且在進行垃圾收集的時候,所有的工作線程都需要停止工作,等待垃圾收集線程完成以后,其他線程才可以繼續工作。
使用算法:復制算法
ParNew 收集器
特點: ParNew 垃圾收集器是Serial收集器的多線程版本。為了利用 CPU 多核多線程的優勢,ParNew 收集器可以運行多個收集線程來進行垃圾收集工作。這樣可以提高垃圾收集過程的效率。
使用算法:復制算法
Parallel Scavenge 收集器
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 垃圾收集器是一款以獲取最短停頓時間為目標的收集器。如下圖所示:
[圖片上傳失敗...(image-4b77a0-1618237334080)]
從圖中可以看出,CMS 收集器的工作過程可以分為 4 個階段:
初始標記(CMS initial mark)階段
并發標記(CMS concurrent mark)階段
重新標記(CMS remark)階段
并發清除((CMS concurrent sweep)階段
使用算法:復制+標記清除
其他
G1 垃圾收集器
特點: 主要步驟:初始標記,并發標記,重新標記,復制清除。
使用算法:復制 + 標記整理
1.10 什么是 Class 文件? Class 文件主要的信息結構有哪些?
Class 文件是一組以 8 位字節為基礎單位的二進制流。各個數據項嚴格按順序排列。
Class 文件格式采用一種類似于 C 語言結構體的偽結構來存儲數據。這樣的偽結構僅僅有兩種數據類型:無符號數和表。
無符號數:是基本數據類型。以 u1、u2、u4、u8 分別代表 1 個字節、2 個字節、4 個字節、8 個字節的無符號數,能夠用來描寫敘述數字、索引引用、數量值或者依照 UTF-8 編碼構成的字符串值。
表:由多個無符號數或者其它表作為數據項構成的復合數據類型。全部表都習慣性地以 _info 結尾。
1.11 對象“對象已死” 是什么概念?
對象不可能再被任何途徑使用,稱為對象已死。
判斷對象已死的方法有:引用計數法與可達性分析算法。