'關於Java面試知識點解析——JVM基礎篇'

"

跳槽時時刻刻都在發生,但是我建議大家跳槽之前,先想清楚為什麼要跳槽。切不可跟風,看到同事一個個都走了,自己也盲目的開始面試起來(期間也沒有準備充分),到底是因為技術原因(影響自己的發展,偏移自己規劃的軌跡),還是錢給少了,不受重視。

準備不充分的面試,完全是浪費時間,更是對自己的不負責(如果title很高,當我沒說)。今天給大家分享下 Java面試知識點解析——JVM基礎篇

"

跳槽時時刻刻都在發生,但是我建議大家跳槽之前,先想清楚為什麼要跳槽。切不可跟風,看到同事一個個都走了,自己也盲目的開始面試起來(期間也沒有準備充分),到底是因為技術原因(影響自己的發展,偏移自己規劃的軌跡),還是錢給少了,不受重視。

準備不充分的面試,完全是浪費時間,更是對自己的不負責(如果title很高,當我沒說)。今天給大家分享下 Java面試知識點解析——JVM基礎篇

關於Java面試知識點解析——JVM基礎篇

1)Java 是如何實現跨平臺的?

注意:跨平臺的是 Java 程序,而不是 JVM。JVM 是用 C/C++ 開發的,是編譯後的機器碼,不能跨平臺,不同平臺下需要安裝不同版本的 JVM

答:我們編寫的 Java 源碼,編譯後會生成一種 .class 文件,稱為字節碼文件。Java 虛擬機(JVM)就是負責將字節碼文件翻譯成特定平臺下的機器碼然後運行,也就是說,只要在不同平臺上安裝對應的 JVM,就可以運行字節碼文件,運行我們編寫的 Java 程序。

而這個過程,我們編寫的 Java 程序沒有做任何改變,僅僅是通過 JVM 這一 “中間層” ,就能在不同平臺上運行,真正實現了 “一次編譯,到處運行” 的目的。

2)什麼是 JVM ?

解析:不僅僅是基本概念,還有 JVM 的作用。

答:JVM,即 Java Virtual Machine,Java 虛擬機。它通過模擬一個計算機來達到一個計算機所具有的的計算功能。JVM 能夠跨計算機體系結構來執行 Java 字節碼,主要是由於 JVM 屏蔽了與各個計算機平臺相關的軟件或者硬件之間的差異,使得與平臺相關的耦合統一由 JVM 提供者來實現。

3)JVM 由哪些部分組成?

解析:這是對 JVM 體系結構的考察

答:JVM 的結構基本上由 4 部分組成:

類加載器,在 JVM 啟動時或者類運行時將需要的 class 加載到 JVM 中

執行引擎,執行引擎的任務是負責執行 class 文件中包含的字節碼指令,相當於實際機器上的 CPU

內存區,將內存劃分成若干個區以模擬實際機器上的存儲、記錄和調度功能模塊,如實際機器上的各種功能的寄存器或者 PC 指針的記錄器等

本地方法調用,調用 C 或 C++ 實現的本地方法的代碼返回結果

4)類加載器是有了解嗎?

解析:底層原理的考察,其中涉及到類加載器的概念,功能以及一些底層的實現。

答:顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。

類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加複雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。

面試官:Java 虛擬機是如何判定兩個 Java 類是相同的?

答:Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的。比如一個 Java 類 com.example.Sample,編譯之後生成了字節代碼文件 Sample.class。兩個不同的類加載器 ClassLoaderA和 ClassLoaderB分別讀取了這個 Sample.class文件,並定義出兩個 java.lang.Class類的實例來表示這個類。這兩個實例是不相同的。對於 Java 虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常 ClassCastException。

5)類加載器是如何加載 class 文件的?

答:下圖所示是 ClassLoader 加載一個 class 文件到 JVM 時需要經過的步驟:

第一個階段是找到 .class 文件並把這個文件包含的字節碼加載到內存中

第二階段又可以分為三個步驟,分別是字節碼驗證、Class 類數據結構分析及相應的內存分配和最後的符號表的鏈接

第三個階段是類中靜態屬性和初始化賦值,以及靜態塊的執行等

面試官:能詳細講講嗎?

答:

1.加載

查找並加載類的二進制數據加載時類加載過程的第一個階段,在加載階段,虛擬機需要完成以下三件事情:

通過一個類的全限定名來獲取其定義的二進制字節流。

將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

在Java堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口。

相對於類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個 java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

2.連接

驗證:確保被加載的類的正確性

驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以 0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。

元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了 java.lang.Object之外。

字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。

驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用 -Xverifynone 參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

準備:為類的靜態變量分配內存,並將其初始化為默認值

準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有以下幾點需要注意:

① 這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。

② 這裡所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

假設一個類變量的定義為: public static int value = 3;

那麼變量value在準備階段過後的初始值為 0,而不是 3,因為這時候尚未開始執行任何 Java 方法,而把 value 賦值為 3 的public static指令是在程序編譯後,存放於類構造器 <clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。

這裡還需要注意如下幾點:

對基本數據類型來說,對於類變量(static)和全局變量,如果不顯式地對其賦值而直接使用,則系統會為其賦予默認的零值,而對於局部變量來說,在使用前必須顯式地為其賦值,否則編譯時不通過。

對於同時被static和final修飾的常量,必須在聲明的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在聲明時顯式地為其賦值,也可以在類初始化時顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予默認零值。

對於引用數據類型reference來說,如數組引用、對象引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予默認的零值,即null。

如果在數組初始化時沒有對數組中的各元素賦值,那麼其中的元素將根據對應的數據類型而被賦予默認的零值。

③ 如果類字段的字段屬性表中存在 ConstantValue 屬性,即同時被 final 和 static 修飾,那麼在準備階段變量 value 就會被初始化為 ConstValue 屬性所指定的值。

假設上面的類變量 value 被定義為: public static final int value = 3;

編譯時 Javac 將會為 value 生成 ConstantValue 屬性,在準備階段虛擬機就會根據 ConstantValue 的設置將 value 賦值為 3。我們可以理解為 static final 常量在編譯期就將其結果放入了調用它的類的常量池中

解析:把類中的符號引用轉換為直接引用

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

3.初始化

初始化,為類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。在Java中對類變量進行初始值設定有兩種方式:

① 聲明類變量是指定初始值

② 使用靜態代碼塊為類變量指定初始值

JVM初始化步驟

1、假如這個類還沒有被加載和連接,則程序先加載並連接該類

2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類

3、假如類中有初始化語句,則系統依次執行這些初始化語句

類初始化時機:只有當對類的主動使用的時候才會導致類的初始化,類的主動使用包括以下六種:

創建類的實例,也就是new的方式

訪問某個類或接口的靜態變量,或者對該靜態變量賦值

調用類的靜態方法

反射(如 Class.forName(“com.shengsiyuan.Test”))

初始化某個類的子類,則其父類也會被初始化

Java虛擬機啟動時被標明為啟動類的類( JavaTest),直接使用 java.exe命令來運行某個主類

結束生命週期

在如下幾種情況下,Java虛擬機將結束生命週期

執行了 System.exit()方法

程序正常執行結束

程序在執行過程中遇到了異常或錯誤而異常終止

由於操作系統出現錯誤而導致Java虛擬機進程終止

6)雙親委派模型(Parent Delegation Model)?

解析:類的加載過程採用雙親委派機制,這種機制能更好的保證 Java 平臺的安全性

答:類加載器 ClassLoader 是具有層次結構的,也就是父子關係,其中,Bootstrap 是所有類加載器的父親,如下圖所示:

該模型要求除了頂層的 Bootstrap class loader 啟動類加載器外,其餘的類加載器都應當有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關係來實現,而是通過組合(Composition)關係來複用父加載器的代碼。每個類加載器都有自己的命名空間(由該加載器及所有父類加載器所加載的類組成,在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類;在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類)

面試官:雙親委派模型的工作過程?

答:

1.當前 ClassLoader 首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。

每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,

等下次加載的時候就可以直接返回了。

2.當前 ClassLoader 的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到 bootstrap ClassLoader.

當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

面試官:為什麼這樣設計呢?

解析:這是對於使用這種模型來組織累加器的好處

答:主要是為了安全性,避免用戶自己編寫的類動態替換 Java 的一些核心類,比如 String,同時也避免了重複加載,因為 JVM 中區分不同類,不僅僅是根據類名,相同的 class 文件被不同的 ClassLoader 加載就是不同的兩個類,如果相互轉型的話會拋java.lang.ClassCaseException.

"

跳槽時時刻刻都在發生,但是我建議大家跳槽之前,先想清楚為什麼要跳槽。切不可跟風,看到同事一個個都走了,自己也盲目的開始面試起來(期間也沒有準備充分),到底是因為技術原因(影響自己的發展,偏移自己規劃的軌跡),還是錢給少了,不受重視。

準備不充分的面試,完全是浪費時間,更是對自己的不負責(如果title很高,當我沒說)。今天給大家分享下 Java面試知識點解析——JVM基礎篇

關於Java面試知識點解析——JVM基礎篇

1)Java 是如何實現跨平臺的?

注意:跨平臺的是 Java 程序,而不是 JVM。JVM 是用 C/C++ 開發的,是編譯後的機器碼,不能跨平臺,不同平臺下需要安裝不同版本的 JVM

答:我們編寫的 Java 源碼,編譯後會生成一種 .class 文件,稱為字節碼文件。Java 虛擬機(JVM)就是負責將字節碼文件翻譯成特定平臺下的機器碼然後運行,也就是說,只要在不同平臺上安裝對應的 JVM,就可以運行字節碼文件,運行我們編寫的 Java 程序。

而這個過程,我們編寫的 Java 程序沒有做任何改變,僅僅是通過 JVM 這一 “中間層” ,就能在不同平臺上運行,真正實現了 “一次編譯,到處運行” 的目的。

2)什麼是 JVM ?

解析:不僅僅是基本概念,還有 JVM 的作用。

答:JVM,即 Java Virtual Machine,Java 虛擬機。它通過模擬一個計算機來達到一個計算機所具有的的計算功能。JVM 能夠跨計算機體系結構來執行 Java 字節碼,主要是由於 JVM 屏蔽了與各個計算機平臺相關的軟件或者硬件之間的差異,使得與平臺相關的耦合統一由 JVM 提供者來實現。

3)JVM 由哪些部分組成?

解析:這是對 JVM 體系結構的考察

答:JVM 的結構基本上由 4 部分組成:

類加載器,在 JVM 啟動時或者類運行時將需要的 class 加載到 JVM 中

執行引擎,執行引擎的任務是負責執行 class 文件中包含的字節碼指令,相當於實際機器上的 CPU

內存區,將內存劃分成若干個區以模擬實際機器上的存儲、記錄和調度功能模塊,如實際機器上的各種功能的寄存器或者 PC 指針的記錄器等

本地方法調用,調用 C 或 C++ 實現的本地方法的代碼返回結果

4)類加載器是有了解嗎?

解析:底層原理的考察,其中涉及到類加載器的概念,功能以及一些底層的實現。

答:顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。

類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加複雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。

面試官:Java 虛擬機是如何判定兩個 Java 類是相同的?

答:Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的。比如一個 Java 類 com.example.Sample,編譯之後生成了字節代碼文件 Sample.class。兩個不同的類加載器 ClassLoaderA和 ClassLoaderB分別讀取了這個 Sample.class文件,並定義出兩個 java.lang.Class類的實例來表示這個類。這兩個實例是不相同的。對於 Java 虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常 ClassCastException。

5)類加載器是如何加載 class 文件的?

答:下圖所示是 ClassLoader 加載一個 class 文件到 JVM 時需要經過的步驟:

第一個階段是找到 .class 文件並把這個文件包含的字節碼加載到內存中

第二階段又可以分為三個步驟,分別是字節碼驗證、Class 類數據結構分析及相應的內存分配和最後的符號表的鏈接

第三個階段是類中靜態屬性和初始化賦值,以及靜態塊的執行等

面試官:能詳細講講嗎?

答:

1.加載

查找並加載類的二進制數據加載時類加載過程的第一個階段,在加載階段,虛擬機需要完成以下三件事情:

通過一個類的全限定名來獲取其定義的二進制字節流。

將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

在Java堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口。

相對於類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個 java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

2.連接

驗證:確保被加載的類的正確性

驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以 0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。

元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了 java.lang.Object之外。

字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。

驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用 -Xverifynone 參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

準備:為類的靜態變量分配內存,並將其初始化為默認值

準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有以下幾點需要注意:

① 這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。

② 這裡所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

假設一個類變量的定義為: public static int value = 3;

那麼變量value在準備階段過後的初始值為 0,而不是 3,因為這時候尚未開始執行任何 Java 方法,而把 value 賦值為 3 的public static指令是在程序編譯後,存放於類構造器 <clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。

這裡還需要注意如下幾點:

對基本數據類型來說,對於類變量(static)和全局變量,如果不顯式地對其賦值而直接使用,則系統會為其賦予默認的零值,而對於局部變量來說,在使用前必須顯式地為其賦值,否則編譯時不通過。

對於同時被static和final修飾的常量,必須在聲明的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在聲明時顯式地為其賦值,也可以在類初始化時顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予默認零值。

對於引用數據類型reference來說,如數組引用、對象引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予默認的零值,即null。

如果在數組初始化時沒有對數組中的各元素賦值,那麼其中的元素將根據對應的數據類型而被賦予默認的零值。

③ 如果類字段的字段屬性表中存在 ConstantValue 屬性,即同時被 final 和 static 修飾,那麼在準備階段變量 value 就會被初始化為 ConstValue 屬性所指定的值。

假設上面的類變量 value 被定義為: public static final int value = 3;

編譯時 Javac 將會為 value 生成 ConstantValue 屬性,在準備階段虛擬機就會根據 ConstantValue 的設置將 value 賦值為 3。我們可以理解為 static final 常量在編譯期就將其結果放入了調用它的類的常量池中

解析:把類中的符號引用轉換為直接引用

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

3.初始化

初始化,為類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。在Java中對類變量進行初始值設定有兩種方式:

① 聲明類變量是指定初始值

② 使用靜態代碼塊為類變量指定初始值

JVM初始化步驟

1、假如這個類還沒有被加載和連接,則程序先加載並連接該類

2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類

3、假如類中有初始化語句,則系統依次執行這些初始化語句

類初始化時機:只有當對類的主動使用的時候才會導致類的初始化,類的主動使用包括以下六種:

創建類的實例,也就是new的方式

訪問某個類或接口的靜態變量,或者對該靜態變量賦值

調用類的靜態方法

反射(如 Class.forName(“com.shengsiyuan.Test”))

初始化某個類的子類,則其父類也會被初始化

Java虛擬機啟動時被標明為啟動類的類( JavaTest),直接使用 java.exe命令來運行某個主類

結束生命週期

在如下幾種情況下,Java虛擬機將結束生命週期

執行了 System.exit()方法

程序正常執行結束

程序在執行過程中遇到了異常或錯誤而異常終止

由於操作系統出現錯誤而導致Java虛擬機進程終止

6)雙親委派模型(Parent Delegation Model)?

解析:類的加載過程採用雙親委派機制,這種機制能更好的保證 Java 平臺的安全性

答:類加載器 ClassLoader 是具有層次結構的,也就是父子關係,其中,Bootstrap 是所有類加載器的父親,如下圖所示:

該模型要求除了頂層的 Bootstrap class loader 啟動類加載器外,其餘的類加載器都應當有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關係來實現,而是通過組合(Composition)關係來複用父加載器的代碼。每個類加載器都有自己的命名空間(由該加載器及所有父類加載器所加載的類組成,在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類;在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類)

面試官:雙親委派模型的工作過程?

答:

1.當前 ClassLoader 首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。

每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,

等下次加載的時候就可以直接返回了。

2.當前 ClassLoader 的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到 bootstrap ClassLoader.

當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

面試官:為什麼這樣設計呢?

解析:這是對於使用這種模型來組織累加器的好處

答:主要是為了安全性,避免用戶自己編寫的類動態替換 Java 的一些核心類,比如 String,同時也避免了重複加載,因為 JVM 中區分不同類,不僅僅是根據類名,相同的 class 文件被不同的 ClassLoader 加載就是不同的兩個類,如果相互轉型的話會拋java.lang.ClassCaseException.

關於Java面試知識點解析——JVM基礎篇

"

跳槽時時刻刻都在發生,但是我建議大家跳槽之前,先想清楚為什麼要跳槽。切不可跟風,看到同事一個個都走了,自己也盲目的開始面試起來(期間也沒有準備充分),到底是因為技術原因(影響自己的發展,偏移自己規劃的軌跡),還是錢給少了,不受重視。

準備不充分的面試,完全是浪費時間,更是對自己的不負責(如果title很高,當我沒說)。今天給大家分享下 Java面試知識點解析——JVM基礎篇

關於Java面試知識點解析——JVM基礎篇

1)Java 是如何實現跨平臺的?

注意:跨平臺的是 Java 程序,而不是 JVM。JVM 是用 C/C++ 開發的,是編譯後的機器碼,不能跨平臺,不同平臺下需要安裝不同版本的 JVM

答:我們編寫的 Java 源碼,編譯後會生成一種 .class 文件,稱為字節碼文件。Java 虛擬機(JVM)就是負責將字節碼文件翻譯成特定平臺下的機器碼然後運行,也就是說,只要在不同平臺上安裝對應的 JVM,就可以運行字節碼文件,運行我們編寫的 Java 程序。

而這個過程,我們編寫的 Java 程序沒有做任何改變,僅僅是通過 JVM 這一 “中間層” ,就能在不同平臺上運行,真正實現了 “一次編譯,到處運行” 的目的。

2)什麼是 JVM ?

解析:不僅僅是基本概念,還有 JVM 的作用。

答:JVM,即 Java Virtual Machine,Java 虛擬機。它通過模擬一個計算機來達到一個計算機所具有的的計算功能。JVM 能夠跨計算機體系結構來執行 Java 字節碼,主要是由於 JVM 屏蔽了與各個計算機平臺相關的軟件或者硬件之間的差異,使得與平臺相關的耦合統一由 JVM 提供者來實現。

3)JVM 由哪些部分組成?

解析:這是對 JVM 體系結構的考察

答:JVM 的結構基本上由 4 部分組成:

類加載器,在 JVM 啟動時或者類運行時將需要的 class 加載到 JVM 中

執行引擎,執行引擎的任務是負責執行 class 文件中包含的字節碼指令,相當於實際機器上的 CPU

內存區,將內存劃分成若干個區以模擬實際機器上的存儲、記錄和調度功能模塊,如實際機器上的各種功能的寄存器或者 PC 指針的記錄器等

本地方法調用,調用 C 或 C++ 實現的本地方法的代碼返回結果

4)類加載器是有了解嗎?

解析:底層原理的考察,其中涉及到類加載器的概念,功能以及一些底層的實現。

答:顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。

類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加複雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。

面試官:Java 虛擬機是如何判定兩個 Java 類是相同的?

答:Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的。比如一個 Java 類 com.example.Sample,編譯之後生成了字節代碼文件 Sample.class。兩個不同的類加載器 ClassLoaderA和 ClassLoaderB分別讀取了這個 Sample.class文件,並定義出兩個 java.lang.Class類的實例來表示這個類。這兩個實例是不相同的。對於 Java 虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常 ClassCastException。

5)類加載器是如何加載 class 文件的?

答:下圖所示是 ClassLoader 加載一個 class 文件到 JVM 時需要經過的步驟:

第一個階段是找到 .class 文件並把這個文件包含的字節碼加載到內存中

第二階段又可以分為三個步驟,分別是字節碼驗證、Class 類數據結構分析及相應的內存分配和最後的符號表的鏈接

第三個階段是類中靜態屬性和初始化賦值,以及靜態塊的執行等

面試官:能詳細講講嗎?

答:

1.加載

查找並加載類的二進制數據加載時類加載過程的第一個階段,在加載階段,虛擬機需要完成以下三件事情:

通過一個類的全限定名來獲取其定義的二進制字節流。

將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

在Java堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口。

相對於類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個 java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

2.連接

驗證:確保被加載的類的正確性

驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以 0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。

元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了 java.lang.Object之外。

字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。

驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用 -Xverifynone 參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

準備:為類的靜態變量分配內存,並將其初始化為默認值

準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有以下幾點需要注意:

① 這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。

② 這裡所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

假設一個類變量的定義為: public static int value = 3;

那麼變量value在準備階段過後的初始值為 0,而不是 3,因為這時候尚未開始執行任何 Java 方法,而把 value 賦值為 3 的public static指令是在程序編譯後,存放於類構造器 <clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。

這裡還需要注意如下幾點:

對基本數據類型來說,對於類變量(static)和全局變量,如果不顯式地對其賦值而直接使用,則系統會為其賦予默認的零值,而對於局部變量來說,在使用前必須顯式地為其賦值,否則編譯時不通過。

對於同時被static和final修飾的常量,必須在聲明的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在聲明時顯式地為其賦值,也可以在類初始化時顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予默認零值。

對於引用數據類型reference來說,如數組引用、對象引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予默認的零值,即null。

如果在數組初始化時沒有對數組中的各元素賦值,那麼其中的元素將根據對應的數據類型而被賦予默認的零值。

③ 如果類字段的字段屬性表中存在 ConstantValue 屬性,即同時被 final 和 static 修飾,那麼在準備階段變量 value 就會被初始化為 ConstValue 屬性所指定的值。

假設上面的類變量 value 被定義為: public static final int value = 3;

編譯時 Javac 將會為 value 生成 ConstantValue 屬性,在準備階段虛擬機就會根據 ConstantValue 的設置將 value 賦值為 3。我們可以理解為 static final 常量在編譯期就將其結果放入了調用它的類的常量池中

解析:把類中的符號引用轉換為直接引用

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

3.初始化

初始化,為類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。在Java中對類變量進行初始值設定有兩種方式:

① 聲明類變量是指定初始值

② 使用靜態代碼塊為類變量指定初始值

JVM初始化步驟

1、假如這個類還沒有被加載和連接,則程序先加載並連接該類

2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類

3、假如類中有初始化語句,則系統依次執行這些初始化語句

類初始化時機:只有當對類的主動使用的時候才會導致類的初始化,類的主動使用包括以下六種:

創建類的實例,也就是new的方式

訪問某個類或接口的靜態變量,或者對該靜態變量賦值

調用類的靜態方法

反射(如 Class.forName(“com.shengsiyuan.Test”))

初始化某個類的子類,則其父類也會被初始化

Java虛擬機啟動時被標明為啟動類的類( JavaTest),直接使用 java.exe命令來運行某個主類

結束生命週期

在如下幾種情況下,Java虛擬機將結束生命週期

執行了 System.exit()方法

程序正常執行結束

程序在執行過程中遇到了異常或錯誤而異常終止

由於操作系統出現錯誤而導致Java虛擬機進程終止

6)雙親委派模型(Parent Delegation Model)?

解析:類的加載過程採用雙親委派機制,這種機制能更好的保證 Java 平臺的安全性

答:類加載器 ClassLoader 是具有層次結構的,也就是父子關係,其中,Bootstrap 是所有類加載器的父親,如下圖所示:

該模型要求除了頂層的 Bootstrap class loader 啟動類加載器外,其餘的類加載器都應當有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關係來實現,而是通過組合(Composition)關係來複用父加載器的代碼。每個類加載器都有自己的命名空間(由該加載器及所有父類加載器所加載的類組成,在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類;在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類)

面試官:雙親委派模型的工作過程?

答:

1.當前 ClassLoader 首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。

每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,

等下次加載的時候就可以直接返回了。

2.當前 ClassLoader 的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到 bootstrap ClassLoader.

當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

面試官:為什麼這樣設計呢?

解析:這是對於使用這種模型來組織累加器的好處

答:主要是為了安全性,避免用戶自己編寫的類動態替換 Java 的一些核心類,比如 String,同時也避免了重複加載,因為 JVM 中區分不同類,不僅僅是根據類名,相同的 class 文件被不同的 ClassLoader 加載就是不同的兩個類,如果相互轉型的話會拋java.lang.ClassCaseException.

關於Java面試知識點解析——JVM基礎篇

關於Java面試知識點解析——JVM基礎篇

這些資料是小編最近花了幾個月的時間整理出來的,想要獲取上面的資料,點擊下面“瞭解更多”即可免費獲取

"

相關推薦

推薦中...