在任何Java面試當(dāng)中JVM的問題都是必不可少的一部分 (一)JVM 基礎(chǔ)知識 (1)Java 是如何實現(xiàn)跨平臺的? 注意:跨平臺的是 Java 程序,而不是 JVM。JVM 是用 C/C 開發(fā)的,是編譯后的機(jī)器碼,不能跨平臺,不同平臺下需要安裝不同版本的 JVM 答:我們編寫的 Java 源碼,編譯后會生成一種 .class 文件,稱為字節(jié)碼文件。Java 虛擬機(jī)(JVM)就是負(fù)責(zé)將字節(jié)碼文件翻譯成特定平臺下的機(jī)器碼然后運行,也就是說,只要在不同平臺上安裝對應(yīng)的 JVM,就可以運行字節(jié)碼文件,運行我們編寫的 Java 程序。 而這個過程,我們編寫的 Java 程序沒有做任何改變,僅僅是通過 JVM 這一 “中間層” ,就能在不同平臺上運行,真正實現(xiàn)了 “一次編譯,到處運行” 的目的。 (2)什么是 JVM ? 解析:不僅僅是基本概念,還有 JVM 的作用。 答:JVM,即 Java Virtual Machine,Java 虛擬機(jī)。它通過模擬一個計算機(jī)來達(dá)到一個計算機(jī)所具有的的計算功能。JVM 能夠跨計算機(jī)體系結(jié)構(gòu)來執(zhí)行 Java 字節(jié)碼,主要是由于 JVM 屏蔽了與各個計算機(jī)平臺相關(guān)的軟件或者硬件之間的差異,使得與平臺相關(guān)的耦合統(tǒng)一由 JVM 提供者來實現(xiàn)。 (3)JVM 由哪些部分組成? 解析:這是對 JVM 體系結(jié)構(gòu)的考察 答:JVM 的結(jié)構(gòu)基本上由 4 部分組成:
(4)類加載器是有了解嗎? 解析:底層原理的考察,其中涉及到類加載器的概念,功能以及一些底層的實現(xiàn)。 答:顧名思義,類加載器(class loader)用來加載 Java 類到 Java 虛擬機(jī)中。一般來說,Java 虛擬機(jī)使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。 類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創(chuàng)建出該類的一個對象。實際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過工具動態(tài)生成的,也可能是通過網(wǎng)絡(luò)下載的。 面試官:Java 虛擬機(jī)是如何判定兩個 Java 類是相同的? 答:Java 虛擬機(jī)不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認(rèn)為兩個類是相同的。即便是同樣的字節(jié)代碼,被不同的類加載器加載之后所得到的類,也是不同的。比如一個 Java 類 com.example.Sample,編譯之后生成了字節(jié)代碼文件 Sample.class。兩個不同的類加載器 ClassLoaderA和 ClassLoaderB分別讀取了這個 Sample.class文件,并定義出兩個 java.lang.Class類的實例來表示這個類。這兩個實例是不相同的。對于 Java 虛擬機(jī)來說,它們是不同的類。試圖對這兩個類的對象進(jìn)行相互賦值,會拋出運行時異常 ClassCastException。 (5)類加載器是如何加載 class 文件的? 答:下圖所示是 ClassLoader 加載一個 class 文件到 JVM 時需要經(jīng)過的步驟: 第一個階段是找到 .class 文件并把這個文件包含的字節(jié)碼加載到內(nèi)存中 第二階段又可以分為三個步驟,分別是字節(jié)碼驗證、Class 類數(shù)據(jù)結(jié)構(gòu)分析及相應(yīng)的內(nèi)存分配和最后的符號表的鏈接 第三個階段是類中靜態(tài)屬性和初始化賦值,以及靜態(tài)塊的執(zhí)行等 面試官:能詳細(xì)講講嗎? 答: 1.加載 查找并加載類的二進(jìn)制數(shù)據(jù)加載時類加載過程的第一個階段,在加載階段,虛擬機(jī)需要完成以下三件事情:
相對于類加載的其他階段而言,加載階段(準(zhǔn)確地說,是加載階段獲取類的二進(jìn)制字節(jié)流的動作)是可控性最強(qiáng)的階段,因為開發(fā)人員既可以使用系統(tǒng)提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。 加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲在方法區(qū)之中,而且在Java堆中也創(chuàng)建一個 java.lang.Class類的對象,這樣便可以通過該對象訪問方法區(qū)中的這些數(shù)據(jù)。 2.連接 驗證:確保被加載的類的正確性 驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。驗證階段大致會完成4個階段的檢驗動作:
驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經(jīng)過反復(fù)驗證,那么可以考慮采用 -Xverifynone 參數(shù)來關(guān)閉大部分的類驗證措施,以縮短虛擬機(jī)類加載的時間。 準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值 準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對于該階段有以下幾點需要注意:
假設(shè)一個類變量的定義為: public static int value = 3; 那么變量value在準(zhǔn)備階段過后的初始值為 0,而不是 3,因為這時候尚未開始執(zhí)行任何 Java 方法,而把 value 賦值為 3 的public static指令是在程序編譯后,存放于類構(gòu)造器 <clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執(zhí)行。
假設(shè)上面的類變量 value 被定義為: public static final int value = 3; 編譯時 Javac 將會為 value 生成 ConstantValue 屬性,在準(zhǔn)備階段虛擬機(jī)就會根據(jù) ConstantValue 的設(shè)置將 value 賦值為 3。我們可以理解為 static final 常量在編譯期就將其結(jié)果放入了調(diào)用它的類的常量池中 解析:把類中的符號引用轉(zhuǎn)換為直接引用 解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類符號引用進(jìn)行。符號引用就是一組符號來描述目標(biāo),可以是任何字面量。 直接引用就是直接指向目標(biāo)的指針、相對偏移量或一個間接定位到目標(biāo)的句柄。 3.初始化 初始化,為類的靜態(tài)變量賦予正確的初始值,JVM負(fù)責(zé)對類進(jìn)行初始化,主要對類變量進(jìn)行初始化。在Java中對類變量進(jìn)行初始值設(shè)定有兩種方式:
JVM初始化步驟
類初始化時機(jī):只有當(dāng)對類的主動使用的時候才會導(dǎo)致類的初始化,類的主動使用包括以下六種:
結(jié)束生命周期 在如下幾種情況下,Java虛擬機(jī)將結(jié)束生命周期
(6)雙親委派模型(Parent Delegation Model)? 解析:類的加載過程采用雙親委派機(jī)制,這種機(jī)制能更好的保證 Java 平臺的安全性 答:類加載器 ClassLoader 是具有層次結(jié)構(gòu)的,也就是父子關(guān)系,其中,Bootstrap 是所有類加載器的父親,如下圖所示: 該模型要求除了頂層的 Bootstrap class loader 啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關(guān)系來實現(xiàn),而是通過組合(Composition)關(guān)系來復(fù)用父加載器的代碼。每個類加載器都有自己的命名空間(由該加載器及所有父類加載器所加載的類組成,在同一個命名空間中,不會出現(xiàn)類的完整名字(包括類的包名)相同的兩個類;在不同的命名空間中,有可能會出現(xiàn)類的完整名字(包括類的包名)相同的兩個類) 面試官:雙親委派模型的工作過程? 答: 1.當(dāng)前 ClassLoader 首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類。
2.當(dāng)前 ClassLoader 的緩存中沒有找到被加載的類的時候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,一直到 bootstrap ClassLoader.
面試官:為什么這樣設(shè)計呢? 解析:這是對于使用這種模型來組織累加器的好處 答:主要是為了安全性,避免用戶自己編寫的類動態(tài)替換 Java 的一些核心類,比如 String,同時也避免了重復(fù)加載,因為 JVM 中區(qū)分不同類,不僅僅是根據(jù)類名,相同的 class 文件被不同的 ClassLoader 加載就是不同的兩個類,如果相互轉(zhuǎn)型的話會拋java.lang.ClassCaseException. (二)JVM 內(nèi)存管理 (1)JVM 內(nèi)存劃分: 答:
(2)Java 的內(nèi)存模型: 答: Java 虛擬機(jī)規(guī)范中試圖定義一種 Java 內(nèi)存模型(Java Memory Model, JMM)來屏蔽掉各層硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓 Java 程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。 Java 內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存(Main Memory)中。每條線程還有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在主內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間的變量值的傳遞均需要通過主內(nèi)存來完成,線程、主內(nèi)存、工作內(nèi)存三者的關(guān)系如上圖。 那如何學(xué)習(xí)才能快速入門并精通呢? 當(dāng)真正開始學(xué)習(xí)的時候難免不知道從哪入手,導(dǎo)致效率低下影響繼續(xù)學(xué)習(xí)的信心。 但最重要的是不知道哪些技術(shù)需要重點掌握,學(xué)習(xí)時頻繁踩坑,最終浪費大量時間,所以有一套實用的視頻課程用來跟著學(xué)習(xí)是非常有必要的。 為了讓學(xué)習(xí)變得輕松、高效,今天給大家免費分享一套阿里架構(gòu)師傳授的一套教學(xué)資源。幫助大家在成為架構(gòu)師的道路上披荊斬棘。 這套視頻課程詳細(xì)講解了(Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu))等這些成為架構(gòu)師必備的內(nèi)容! 而且還把框架需要用到的各種程序進(jìn)行了打包,根據(jù)基礎(chǔ)視頻可以讓你輕松搭建分布式框架環(huán)境,像在企業(yè)生產(chǎn)環(huán)境一樣進(jìn)行學(xué)習(xí)和實踐。 后臺私信回復(fù)“架構(gòu)” 就可以馬上免費獲得這套價值一萬八的內(nèi)部教材! 最后,做一個愛思考,懂思考,會思考的程序員。 |
|