JVM 面試必備(下)

類加載的5個過程

類加載的本質(zhì)

將描述類的數(shù)據(jù) 從Class文件加載到內(nèi)存并且對數(shù)據(jù)進行校驗 轉(zhuǎn)換解析和初始化 最終新城虛擬機直接使用java使用類型

類加載過程

  1. 加載

    • 作用

      將外部的Class文件加載到虛擬機并且存儲到方法區(qū)內(nèi)

    • 具體流程

      1. 通過類名的全限定名來獲取定義此類的二進制數(shù)據(jù)

      2. 將這個字節(jié)流所代表的的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)

      3. 在內(nèi)存中生成一個代表這個類的java.lang.class對象 作為方法區(qū)該類的各種數(shù)據(jù)的訪問入口

    • 注意

      • 數(shù)組類是通過java虛擬機直接創(chuàng)建 不通過類的加載機制
  2. 驗證

    • 作用 確

      保加載進來的class文件包含的信息符合jvm的要求

    • 具體流程

      1. 文件格式的校驗

      2. 元數(shù)據(jù)校驗

      3. 字節(jié)碼校驗

      4. 符號引用校驗

  3. 準(zhǔn)備

    • 作用

      為類變量分配內(nèi)存 并且設(shè)置類變量的初始值

    • 具體流程

      1. 為類的static變量在方法區(qū)中分配內(nèi)存

      2. 將上述變量的初始值設(shè)置為0

    • 注意

      • 實例變量不在該階段分配內(nèi)存

      • 若該類為常量(final修飾) 直接復(fù)制開發(fā)者定義的值

  4. 解析

    • 作用

      將常量池內(nèi)的符號引用轉(zhuǎn)為直接引用

    • 具體流程

      解析對象(類/接口) 方法 (類方法 接口方法 方法類型 方法句柄) 字段

    • 注意

      • 實例變量不在該階段分配內(nèi)存

      • 因為類方法和私有方法符合 "編譯器可知 , 運行期不可變" 的要求 即不會被繼承或者重寫 所以適合類加載過程進行解析

      • 若類變量為常量 (final 修飾 ) 則直接賦值開發(fā)者定義的值

  5. 初始化

    • 作用

      初始化類變量 靜態(tài)語句塊

    • 具體流程

      1. 生成類構(gòu)造器 clinit() 即合并所有類變量和靜態(tài)語句塊

      2. 執(zhí)行clinit()方法

    • 注意

      • 類構(gòu)造器clinit區(qū)別于類構(gòu)造器 init

        • 不需要調(diào)用父類構(gòu)造器

        • 子類clinit執(zhí)行前 父類的clinit一定會被執(zhí)行

        • 虛擬機第一個執(zhí)行的clinit是 java.lang.object

      • 靜態(tài)語句塊只可被賦值不能被訪問

      • 接口與類不同 執(zhí)行子接口的clinit并不需要執(zhí)行負(fù)借口的clinit

對象的創(chuàng)建 內(nèi)存分配 訪問定位

對象的創(chuàng)建

                    A a =new A(); //當(dāng)遇到關(guān)鍵字new指令時,Java對象創(chuàng)建過程便開始</pre>
類加載過程.png
加載過程
類加載檢查
  1. 檢查該new指令的參數(shù) 是否在常量池中定位到了一個類的符號引用 沒有即創(chuàng)建對象失敗

  2. 檢查該類符號引用代表的類是否已經(jīng)被加載,解析和初始化過

  3. 如果沒有 需要先執(zhí)行類的加載過程

為對象分配內(nèi)存

對象所需要的內(nèi)存大小在類加載完成后便可以完全確定

內(nèi)存分配 根據(jù)java堆內(nèi)存是否絕對規(guī)整分為

  1. 指針碰撞 Compat 收集器

    1. 假設(shè)java堆內(nèi)存絕對規(guī)整 內(nèi)存分配采用指針碰撞

    2. 分配形式: 已使用內(nèi)存在一邊 未使用的在另一邊 中間放一個座位分界點的指示器

    3. 那么 分配對象內(nèi)存 = 指針針向未使用內(nèi)存一定一段與對象大小相等的距離

  2. 空閑列表 CMS 收集器

    1. 假設(shè)java堆內(nèi)存不規(guī)整 內(nèi)存分配將采用空閑列表

    2. 分配形式 :虛擬機維護著一個記錄可用內(nèi)存塊的列表 在分配時從列表中找到一塊足夠大的空間劃分給對象實例 ,并更新列表上的記錄

內(nèi)存創(chuàng)建在虛擬機中非常常見 存在并發(fā)情況下也會引起線程不安全

解決辦法

  1. 同步處理分配內(nèi)存空間 虛擬機采用CAS + 失敗重試 保證更新操作的原子性

  2. 把內(nèi)存分配行為按照線程劃分在不同的內(nèi)存空間進行

    1. 即每個線程在 Java堆中預(yù)先分配一小塊內(nèi)存(本地線程分配緩沖(Thread Local Allocation Buffer ,TLAB)),哪個線程要分配內(nèi)存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時才需要同步鎖。

    2. 虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數(shù)來設(shè)定。

將內(nèi)存空間初始化為0

內(nèi)存分配完成后 虛擬機需要將分配的內(nèi)存空間初始化為0

  1. 保證對象的實例字段在使用時不可復(fù)制就能直接適應(yīng) (默認(rèn)為0)
  1. 如果使用本線程分配緩沖(TLAB) 這一工作過程可以提前至TLAB分配時進行
對對象進行必要的設(shè)置

設(shè)置對象是哪個類的實例 如何找到類的源數(shù)據(jù) 對象的哈希嗎 對象的GC分代信息等

這個信息存放在對象的對象頭中

對象的內(nèi)存分配

在java對象創(chuàng)建后 打底是如何被存儲在java內(nèi)存中的呢

在java虛擬機中 對象內(nèi)存中 存儲布局可以分為三塊

1. 對象頭
1.1 對象自身的運行時數(shù)據(jù) ( Mark Word )
  1. 如Hash 碼 GC分代 鎖狀態(tài) 線程持有的鎖 偏向線程 id 偏向時間戳

  2. 該部分?jǐn)?shù)據(jù)設(shè)計成1個非固定的數(shù)據(jù)結(jié)構(gòu) 一邊在績效的空間存儲更多信息

1.2 對象類型指針
  1. 對象指向它的類元數(shù)據(jù)的指針

  2. 虛擬機通過這個指針確定這個對象是哪個類的實例

1.3 數(shù)組的對象頭

如果是對象是數(shù)組 name在對象頭中還有一塊記錄數(shù)組長度的數(shù)據(jù)

因為虛擬機可以通過普通java 對象的元數(shù)據(jù)確定對象的大小 但是從數(shù)組的元數(shù)據(jù)中無法確定數(shù)組的大小

2. 實例數(shù)據(jù)

存儲的信息 對象真正有效的信息

代碼中定義的字段內(nèi)容

  • 這部分?jǐn)?shù)據(jù)的存儲順序會受到虛擬機分配參數(shù)(FieldAllocationStyle)和字段在Java源碼中定義順序的影響。
3. 對齊填充 (非必須)

存儲 占位符

占位作用

因為對象的大小必須是8字節(jié)的整數(shù)倍(即對象的大小不是8字節(jié)的整數(shù)倍),就需要通過對齊填充來補全。

對象的訪問定位

對象建立后 如何去訪問對象?

實際上 需要訪問的是 對象類型數(shù)據(jù) 和 對象實例數(shù)據(jù)

java 程序 通過 棧上的引用類型(reference) 來訪問java棧上的對象

  • 句柄訪問

  • 直接指針訪問

JVM 分派

分派: 確定執(zhí)行那個方法的過程

靜態(tài)分派

1.1 定義

根據(jù)變量的靜態(tài)類型進行方法分派的行為 根據(jù)變量的靜態(tài)類型 確定執(zhí)行那個方法 發(fā)生在編譯器 不由JVM來執(zhí)行

public class Test { 
    // 類定義
    static abstract class Human { 
    } 
    // 繼承自抽象類Human
    static class Man extends Human { 
    } 
    static class Woman extends Human { 
    } 
    // 可供重載的方法
    public void sayHello(Human guy) { 
        System.out.println("hello,Human!"); 
    } 
 
    public void sayHello(Man guy) { 
        System.out.println("hello Man!"); 
    } 
 
    public void sayHello(Woman guy) { 
        System.out.println("hello Woman!"); 
    } 

// 測試代碼
    public static void main(String[] args) { 
        Human man = new Man(); 
        Human woman = new Woman(); 
        Test test = new Test(); 

        test.sayHello(man); 
        test.sayHello(woman); 
    } 
}

// 運行結(jié)果
hello,Human! 
hello,Human!
1.2 方法重載

重載=靜態(tài)分派 =根據(jù)變量的靜態(tài)類型確定執(zhí)行那個重載方法

1.3 變量的靜態(tài)類型發(fā)生變化

強制裝換類型 改變變量的靜態(tài)類型

    Human man = new Man(); 
    test.sayHello((Man)man); 
1.4 靜態(tài)分配的優(yōu)先級匹配
  • 靜態(tài)分派優(yōu)先選擇參數(shù)類型一致的重載方法

  • 沒有最合適的方式 進行重載時 會選擇(第二優(yōu)先級)的方法重載 基本數(shù)據(jù)類型優(yōu)先級分配

第二優(yōu)先級順序 : char > int > long > float > double > character > serializable> object .. args..

最后的args 為可變長參數(shù) 可以理解為數(shù)組

因為char轉(zhuǎn)為byte 或者short過程不安全 所以不會選擇參數(shù)類型為byte或者short進行重載

  • 引用類型分配 根據(jù)繼承關(guān)系進行優(yōu)先級匹配

動態(tài)分派

2.1 定義

根據(jù)變量的動態(tài)類型確定執(zhí)行那個方法

// 定義類
    class Human { 
        public void sayHello(){ 
            System.out.println("Human say hello"); 
 
        } 
    } 
 
// 繼承自 抽象類Human 并 重寫sayHello()
    class Man extends Human { 
        @Override 
        protected void sayHello() { 
            System.out.println("man say hello"); 
 
        } 
    } 
 
    class Woman extends Human { 
        @Override 
        protected void sayHello() { 
            System.out.println("woman say hello"); 
 
        } 
    } 

// 測試代碼
    public static void main(String[] args) { 

        // 情況1
        Human man = new man(); 
        man.sayHello(); 

        // 情況2
        man = new Woman(); 
        man.sayHello(); 
    } 
}

// 運行結(jié)果
man say hello
woman say hello

// 原因解析
// 1. 方法重寫(Override) = 動態(tài)分派 = 根據(jù) 變量的動態(tài)類型 確定執(zhí)行(重寫)哪個方法
// 2. 對于情況1:根據(jù)變量(Man)的動態(tài)類型(man)確定調(diào)用man中的重寫方法sayHello()
// 3. 對于情況2:根據(jù)變量(Man)的動態(tài)類型(woman)確定調(diào)用woman中的重寫方法sayHello()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容