不止面試—jvm類加載面試題詳解

面試題

帶着問題學習是最高效的,本次咱們將嘗試回答如下問題:html

  1. 什麼是類的加載?
  2. 哪些狀況會觸發類的加載?
  3. 講一下JVM加載一個類的過程
  4. 何時會爲變量分配內存?
  5. JVM的類加載機制是什麼?
  6. 雙親委派機制能夠打破嗎?爲何

答案放在文章的最後,來不及看原理也能夠直接跳到最後直接看答案。java

深刻原理

類的生命週期

類的生命週期相信你們已經耳熟能詳,就像下面這樣:面試

file

不過這東西老是背了就忘,忘了又背,就像馬什麼梅同樣,對吧?數組

其實理解以後,基本上就不會再忘了。緩存

加載

加載主要作三件事:安全

  1. 找到類文件(經過類的全限定名來獲取定義此類的二進制字節流)
  2. 放入方法區(將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構)
  3. 開個入口(生成一個表明此類的java.lang.Class對象,做爲訪問方法區這些數據結構的入口)

總的來說,這一步就是經過類加載器把類讀入內存。須要注意的是,第三步雖然生成了對象,但並不在堆裏,而是在方法區裏。數據結構

鏈接

鏈接分爲三步,通常面試都比較喜歡問準備這一步。jvm

校驗

顧名思義,檢查Class文件的字節流中包含的信息是否符合當前虛擬機的要求。學習

準備

這一步中將爲靜態變量和靜態常量分配內存,並賦值。.net

須要注意的是,靜態變量只會給默認值。好比下面這個:

public static int value = 123;

此時賦給value的值是0,不是123。

靜態常量(static final修飾的)則會直接賦值。好比下面這個:

public static final int value = 123;

此時賦給value的值是123。

解析

解析階段就是jvm將常量池的符號引用替換爲直接引用。

恩......啥是常量池?啥是符號引用?啥是直接引用?

常量池咱們放在jvm內存結構裏說。先來講下什麼是符號引用和直接引用。

符號引用和直接引用

假設有一個Worker類,包含了一個Car類的run()方法,像下面這樣:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //這段代碼在Worker類中的二進制表示爲符號引用        
    }
    ......
}

在解析階段以前,Worker類並不知道car.run()這個方法內存的什麼地方,因而只能用一個字符串來表示這個方法。該字符串包含了足夠的信息,好比類的信息,方法名,方法參數等,以供實際使用時能夠找到相應的位置。

這個字符串就被稱爲符號引用

在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,而後把符號引用替換成直接指向目標的指針、句柄、偏移量等,這以後就能夠直接使用了。

這些直接指向目標的指針、句柄、偏移量就被成爲直接引用

初始化

類的初始化的主要工做是爲靜態變量賦程序設定的初值。

還記得上面的靜態變量嗎:

public static int value = 123;

通過這一步,value的值終因而123了。

總結以下圖:

file

類初始化的條件

Java虛擬機規範中嚴格規定了有且只有五種狀況必須對類進行初始化:

  1. 使用new字節碼指令建立類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。
  2. 經過java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則要首先進行初始化。
  3. 當初始化一個類的時候,若是發現其父類沒有進行過初始化,則首先觸發父類初始化。
  4. 當虛擬機啓動時,用戶須要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。
  5. 使用jdk1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,而且這個方法句柄對應的類沒有進行初始化,則須要先觸發其初始化。

除了以上這五種狀況,其餘任何狀況都不會觸發類的初始化。

好比下面這幾種狀況就不會觸發類初始化:

  1. 經過子類調用父類的靜態字段。此時父類符合狀況一,而子類不符合任何狀況。因此只有父類被初始化。
  2. 經過數組來引用類,不會觸發類的初始化。由於new的是數組,而不是類。
  3. 調用類的靜態常量不會觸發類的初始化,由於靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類。

類加載機制

類加載器

在上面我們曾經說到,加載階段須要「經過一個類的全限定名來獲取描述此類的二進制字節流」。這件事情就是類加載器在作。

jvm自帶三種類加載器,分別是:

  1. 啓動類加載器。
  2. 擴展類加載器。
  3. 應用程序類加載器

他們的繼承關係以下圖:

file

雙親委派

雙親委派機制工做過程以下:

  1. 當前ClassLoader首先從本身已經加載的類中查詢是否此類已經加載,若是已經加載則直接返回原來已經加載的類。每一個類加載器都有本身的加載緩存,當一個類被加載了之後就會放入緩存,等下次加載的時候就能夠直接返回了。

  2.  當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用一樣的策略,首先查看本身的緩存,而後委託父類的父類去加載,一直到bootstrp ClassLoader.

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

爲啥要搞這麼複雜?本身處理很差嗎?

雙親委派的優勢以下:

  1. 避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 爲了安全。避免核心類,好比String被替換。

打破雙親委派

「雙親委派」機制只是Java推薦的機制,並非強制的機制。

好比JDBC就打破了雙親委派機制。它經過Thread.currentThread().getContextClassLoader()獲得線程上下文加載器來加載Driver實現類,從而打破了雙親委派機制。

至於爲何,之後再說吧。

答案

如今,咱們能夠回答文章開頭提出的問題了。儘可能在理解的基礎上回答,不須要死記硬背。

  1. 什麼是類的加載?

    JVM把經過類名得到類的二進制流以後,把類放入方法區,並建立入口對象的過程被稱爲類的加載。通過加載,類就被放到內存裏了。

  2. 哪些狀況會觸發類的初始化?

    類在5種狀況下會被初始化:

    第一,假如這個類是入口類,他會被初始化。

    第二,使用new建立對象,或者調用類的靜態變量,類會被初始化。不過靜態常量不算。

    第三,經過反射獲取類,類會被初始化

    第四,若是子類被初始化,他的父類也會被初始化。

    第五,使用jdk1.7的動態語言支持時,調用到靜態句柄,也會被初始化。

  3. 講一下JVM加載一個類的過程

    同問題1。不過這裏也能夠問下面試官是否是想問類的生命週期。若是是問類的生命週期,能夠回答有」加載、鏈接、初始化、使用、卸載「五個階段,鏈接又能夠分爲」校驗、準備、解析「三個階段。

  4. 何時會爲變量分配內存?

    在準備階段爲靜態變量分配內存。

  5. JVM的類加載機制是什麼?

    雙親委派機制,類加載器會先讓本身的父類來加載,父類沒法加載的話,纔會本身來加載。

  6. 雙親委派機制能夠打破嗎?爲何

    能夠打破,好比JDBC使用線程上下文加載器打破了雙親委派機制。緣由是JDBC只提供了接口,並無提供實現。這個問題能夠再看下引用文獻的內容。

引用文獻

Java何時會觸發類初始化及原理

Java 符號引用 與 直接引用

符號引用和直接引用

JVM中的直接引用和符號引用

面試題:JVM類加載機制詳解(一)JVM類加載過程

深刻理解雙親委託機制

雙親委派模式破壞-JDBC

相關文章
相關標籤/搜索