国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

Java虛擬機之類(lèi)加載

發(fā)布時(shí)間:2021-07-06 11:13 來(lái)源:腳本之家 閱讀:0 作者:氷泠 欄目: 開(kāi)發(fā)技術(shù)

目錄

一、類(lèi)加載流程

類(lèi)加載的流程可以簡(jiǎn)單分為三步:

  • 加載
  • 連接
  • 初始化

而其中的連接又可以細分為三步:

  • 驗證
  • 準備
  • 解析

下面會(huì )分別對各個(gè)流程進(jìn)行介紹。

1.1 類(lèi)加載條件

在了解類(lèi)接在流程之前,先來(lái)看一下觸發(fā)類(lèi)加載的條件。

JVM不會(huì )無(wú)條件加載類(lèi),只有在一個(gè)類(lèi)或接口在初次使用的時(shí)候,必須進(jìn)行初始化。這里的使用是指主動(dòng)使用,主動(dòng)使用包括如下情況:

  • 創(chuàng )建一個(gè)類(lèi)的實(shí)例的時(shí)候:比如使用new創(chuàng )建,或者使用反射、克隆、反序列化
  • 調用類(lèi)的靜態(tài)方法的時(shí)候:比如使用invokestatic指令
  • 使用類(lèi)或接口的靜態(tài)字段:比如使用getstatic/putstatic指令
  • 使用java.lang.reflect中的反射類(lèi)方法時(shí)
  • 初始化子類(lèi)時(shí),要求先初始化父類(lèi)
  • 含有main()方法的類(lèi)

除了以上情況外,其他情況屬于被動(dòng)使用,不會(huì )引起類(lèi)的初始化。

比如下面的例子:

public class Main {
    public static void main(String[] args){
        System.out.println(Child.v);
    }
}

class Parent{
    static{
        System.out.println("Parent init");
    }
    public static int v = 100;
}

class Child extends Parent{
    static {
        System.out.println("Child init");
    }
}

輸出如下:

Parent init
100

而加上類(lèi)加載參數-XX:+TraceClassLoading后,可以看到Child確實(shí)被加載了:

[0.068s][info   ][class,load] com.company.Main
[0.069s][info   ][class,load] com.company.Parent
[0.069s][info   ][class,load] com.company.Child
Parent init
100

但是并沒(méi)有進(jìn)行初始化。另外一個(gè)例子是關(guān)于final的,代碼如下:

public class Main {
    public static void main(String[] args){
        System.out.println(Test.STR);
    }
}

class Test{
    static{
        System.out.println("Test init");
    }
    public static final String STR = "Hello";
}

輸出如下:

[0.066s][info   ][class,load] com.company.Main
Hello

Test類(lèi)根本沒(méi)有被加載,因為final被做了優(yōu)化,編譯后的Main.class中,并沒(méi)有引用Test類(lèi):

0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #4                  // String Hello
5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

在字節碼偏移3的位置,通過(guò)ldc將常量池第4項入棧,此時(shí)在字節碼文件中常量池第4項為:

#3 = Class              #24            // com/company/Test
#4 = String             #25            // Hello
#5 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V

因此并沒(méi)有對Test類(lèi)進(jìn)行加載,只是直接引用常量池中的常量,因此輸出沒(méi)有Test的加載日志。

1.2 加載

類(lèi)加載的時(shí)候,JVM必須完成以下操作:

  • 通過(guò)類(lèi)的全名獲取二進(jìn)制數據
  • 解析類(lèi)的二進(jìn)制數據流為方法區內的數據結構
  • 創(chuàng )建java.lang.Class類(lèi)的實(shí)例,表示該類(lèi)型

第一步獲取二進(jìn)制數據流,途徑有很多,包括:

  • 字節碼文件
  • JAR/ZIP壓縮包
  • 從網(wǎng)絡(luò )加載

等等,獲取到二進(jìn)制數據流后,JVM進(jìn)行處理并轉化為一個(gè)java.lang.Class實(shí)例。

1.3 驗證

驗證的操作是確保加載的字節碼是合法、合理并且規范的。步驟簡(jiǎn)略如下:

  • 格式檢查:判斷二進(jìn)制數據是否符合格式要求和規范,比如是否以魔數開(kāi)頭,主版本號和小版本號是否在當前JVM支持范圍內等等
  • 語(yǔ)義檢查:比如是否所有類(lèi)都有父類(lèi)存在,一些被定義為final的方法或類(lèi)是否被重載了或者繼承了,是否存在不兼容方法等等
  • 字節碼驗證:會(huì )試圖通過(guò)對字節碼流的分析,判斷字節碼是否可以正確被執行,比如是否會(huì )跳轉到一條不存在的指令,函數調用是否傳遞了正確的參數等等,但是卻無(wú)法100%判斷一段字節碼是否可以被安全執行,只是盡可能檢查出可以預知的明顯問(wèn)題。如果無(wú)法通過(guò)檢查,則不會(huì )加載這個(gè)類(lèi),如果通過(guò)了檢查,也不能說(shuō)明這個(gè)類(lèi)完全沒(méi)有問(wèn)題
  • 符號引用驗證:檢查類(lèi)或方法是否確實(shí)存在,并且確定當前類(lèi)有沒(méi)有權限訪(fǎng)問(wèn)這些數據,比如無(wú)法找到一個(gè)類(lèi)就拋出NoClassDefFoundError,無(wú)法找到方法就拋出NoSuchMethodError

1.4 準備

類(lèi)通過(guò)驗證后,就會(huì )進(jìn)入準備階段,在這個(gè)階段,JVM為會(huì )類(lèi)分配相應的內存空間,并設置初始值,比如:

  • int初始化為0
  • long初始化為0L
  • double初始化為0f
  • 引用初始化為null

如果存在常量字段,那么這個(gè)階段也會(huì )為常量賦值。

1.5 解析

解析就是將類(lèi)、接口、字段和方法的符號引用轉為直接引用。符號引用就是一些字面量引用,和JVM的內存數據結構和內存布局無(wú)關(guān),由于在字節碼文件中,通過(guò)常量池進(jìn)行了大量的符號引用,這個(gè)階段就是將這些引用轉為直接引用,得到類(lèi)、字段、方法在內存中的指針或直接偏移量。

另外,由于字符串有著(zhù)很重要的作用,JVMString進(jìn)行了特別的處理,直接使用字符串常量時(shí),就會(huì )在類(lèi)中出現CONSTANT_String,并且會(huì )引用一個(gè)CONSTANT_UTF8常量項。JVM運行時(shí),內部的常量池中會(huì )維護一張字符串拘留表(intern),會(huì )保存其中出現過(guò)的所有字符串常量,并且沒(méi)有重復項。使用String.intern()可以獲得一個(gè)字符串在拘留表的引用,比如下面代碼:

public static void main(String[] args){
    String a = 1 + String.valueOf(2) + 3;
    String b = "123";
    System.out.println(a.equals(b));
    System.out.println(a == b);
    System.out.println(a.intern() == b);
}

輸出:

true
false
true

這里b就是常量本身,因此a.intern()返回在拘留表的引用后就是b本身,比較結果為真。

1.6 初始化

初始化階段會(huì )執行類(lèi)的初始化方法<clint>,<clint>是由編譯期生成的,由靜態(tài)成員的賦值語(yǔ)句以及static語(yǔ)句共同產(chǎn)生。

另外,加載一個(gè)類(lèi)的時(shí)候,JVM總是會(huì )試圖加載該類(lèi)的父類(lèi),因此父類(lèi)的<clint>方法總是在子類(lèi)的<clint>方法之前被調用。另一方面,需要注意的是<clint>會(huì )確保在多線(xiàn)程環(huán)境下的安全性,也就是多個(gè)線(xiàn)程同時(shí)初始化同一個(gè)類(lèi)時(shí),只有一個(gè)線(xiàn)程可以進(jìn)入<clint>方法,換句話(huà)說(shuō),在多線(xiàn)程下可能會(huì )出現死鎖,比如下面代碼:

package com.company;

import java.util.concurrent.TimeUnit;

public class Main extends Thread{
    private char flag;
    public Main(char flag){
        this.flag = flag;
    }
    
    public static void main(String[] args){
        Main a = new Main('A');
        a.start();
        Main b = new Main('B');
        b.start();
    }

    @Override
    public void run() {
        try{
            Class.forName("com.company.Static"+flag);
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

class StaticA{
    static {
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        try{
            Class.forName("com.company.StaticB");
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
        System.out.println("StaticA init ok");
    }
}

class StaticB{
    static {
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        try{
            Class.forName("com.company.StaticA");
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
        System.out.println("StaticB init ok");
    }
}

在加載StaticA的時(shí)候嘗試加載StaticB,但是由于StaticB已經(jīng)被加載中,因此加載StaticA的線(xiàn)程會(huì )阻塞在Class.forName("com.company.StaticB")處,同理加載StaticB的線(xiàn)程會(huì )阻塞在Class.forName("com.company.StaticA")處,這樣就出現死鎖了。

二、ClassLoader

2.1 ClassLoader簡(jiǎn)介

ClassLoader是類(lèi)加載的核心組件,所有的Class都是由ClassLoader加載的,ClassLoader通過(guò)各種各樣的方式將Class信息的二進(jìn)制數據流讀入系統,然后交給JVM進(jìn)行連接、初始化等操作。因此ClassLoader負責類(lèi)的加載流程,無(wú)法通過(guò)ClassLoader改變類(lèi)的連接和初始化行為。

ClassLoader是一個(gè)抽象類(lèi),提供了一些重要接口定義加載流程和加載方式,主要方法如下:

public Class<?> loadClass(String name) throws ClassNotFoundException:給定一個(gè)類(lèi)名,加載一個(gè)類(lèi),返回這個(gè)類(lèi)的Class實(shí)例,找不到拋出異常

protected final Class<?> defineClass(byte[] b, int off, int len):根據給定字節流定義一個(gè)類(lèi),offlen表示在字節數組中的偏移和長(cháng)度,這是一個(gè)protected方法,在自定義子類(lèi)中才能使用

protected Class<?> findClass(String name) throws ClassNotFoundException:查找一個(gè)類(lèi),會(huì )在loadClass中被調用,用于自定義查找類(lèi)的邏輯

protected Class<?> findLoadedClass(String name):尋找一個(gè)已經(jīng)加載的類(lèi)

2.2 類(lèi)加載器分類(lèi)

在標準的Java程序中,JVM會(huì )創(chuàng )建3類(lèi)加載器為整個(gè)應用程序服務(wù),分別是:

  • 啟動(dòng)類(lèi)加載器:Bootstrap ClassLoader
  • 擴展類(lèi)加載器:Extension ClassLoader
  • 應用類(lèi)加載器(也叫系統類(lèi)加載器):App ClassLoader

另外,在程序中還可以定義自己的類(lèi)加載器,從總體看,層次結構如下:

一般來(lái)說(shuō)各個(gè)加載器負責的范圍如下:

  • 啟動(dòng)類(lèi)加載器:負責加載系統的核心類(lèi),比如rt.jar包中的類(lèi)
  • 擴展類(lèi)加載器:負責加載lib/ext/*.jar下的類(lèi)
  • 應用類(lèi)加載器:負責加載用戶(hù)程序的類(lèi)
  • 自定義加載器:加載一些特殊途徑的類(lèi),一般是用戶(hù)程序的類(lèi)

2.3 雙親委派

默認情況下,類(lèi)加載使用雙親委派加載的模式,具體來(lái)說(shuō),就是類(lèi)在加載的時(shí)候,會(huì )判斷當前類(lèi)是否已經(jīng)被加載,如果已經(jīng)被加載,那么直接返回已加載的類(lèi),如果沒(méi)有,會(huì )先請求雙親加載,雙親也是按照一樣的流程先判斷是否已加載,如果沒(méi)有在此委托雙親加載,如果雙親加載失敗,則會(huì )自己加載。

在上圖中,應用類(lèi)加載器的雙親為擴展類(lèi)加載器,擴展類(lèi)加載器的雙親為啟動(dòng)類(lèi)加載器,當系統需要加載一個(gè)類(lèi)的時(shí)候,會(huì )先從底層類(lèi)加載器開(kāi)始進(jìn)行判斷,當需要加載的時(shí)候會(huì )從頂層開(kāi)始加載,依次向下嘗試直到加載成功。

在所有加載器中,啟動(dòng)類(lèi)加載器是最特別的,并不是使用Java語(yǔ)言實(shí)現,在Java中沒(méi)有對象與之相對應,系統核心類(lèi)就是由啟動(dòng)類(lèi)加載器進(jìn)行加載的。換句話(huà)說(shuō),如果嘗試在程序中獲取啟動(dòng)類(lèi)加載器,得到的值是null

System.out.println(String.class.getClassLoader() == null);

輸出結果為真。

到此這篇關(guān)于Java虛擬機之類(lèi)加載的文章就介紹到這了,更多相關(guān)JVM類(lèi)加載內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。

日韩免费无码一区二区视频| GOGOGO在线高清免费完整版| 波多野结衣在线精品视频| 亚洲AV伊人久久综合密臀性色| 国产精品亚洲二区在线播放| 色婷婷五月综合亚洲小说|