最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java虛擬機(jī)之類加載

 更新時間:2021年05月11日 10:24:19   作者:氷泠  
這篇文章主要介紹了Java虛擬機(jī)之類加載,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下

一、類加載流程

類加載的流程可以簡單分為三步:

  • 加載
  • 連接
  • 初始化

而其中的連接又可以細(xì)分為三步:

  • 驗(yàn)證
  • 準(zhǔn)備
  • 解析

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

1.1 類加載條件

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

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

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

除了以上情況外,其他情況屬于被動使用,不會引起類的初始化。

比如下面的例子:

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

而加上類加載參數(shù)-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

但是并沒有進(jìn)行初始化。另外一個例子是關(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類根本沒有被加載,因?yàn)?code>final被做了優(yōu)化,編譯后的Main.class中,并沒有引用Test類:

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

在字節(jié)碼偏移3的位置,通過ldc將常量池第4項(xiàng)入棧,此時在字節(jié)碼文件中常量池第4項(xiàng)為:

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

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

1.2 加載

類加載的時候,JVM必須完成以下操作:

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

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

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

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

1.3 驗(yàn)證

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

在這里插入圖片描述

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

1.4 準(zhǔn)備

類通過驗(yàn)證后,就會進(jìn)入準(zhǔn)備階段,在這個階段,JVM為會類分配相應(yīng)的內(nèi)存空間,并設(shè)置初始值,比如:

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

如果存在常量字段,那么這個階段也會為常量賦值。

1.5 解析

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

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

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本身,比較結(jié)果為真。

1.6 初始化

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

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

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

二、ClassLoader

2.1 ClassLoader簡介

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

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

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

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

protected Class<?> findClass(String name) throws ClassNotFoundException:查找一個類,會在loadClass中被調(diào)用,用于自定義查找類的邏輯

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

2.2 類加載器分類

在標(biāo)準(zhǔn)的Java程序中,JVM會創(chuàng)建3類加載器為整個應(yīng)用程序服務(wù),分別是:

  • 啟動類加載器:Bootstrap ClassLoader
  • 擴(kuò)展類加載器:Extension ClassLoader
  • 應(yīng)用類加載器(也叫系統(tǒng)類加載器):App ClassLoader

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

在這里插入圖片描述

一般來說各個加載器負(fù)責(zé)的范圍如下:

  • 啟動類加載器:負(fù)責(zé)加載系統(tǒng)的核心類,比如rt.jar包中的類
  • 擴(kuò)展類加載器:負(fù)責(zé)加載lib/ext/*.jar下的類
  • 應(yīng)用類加載器:負(fù)責(zé)加載用戶程序的類
  • 自定義加載器:加載一些特殊途徑的類,一般是用戶程序的類

2.3 雙親委派

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

在這里插入圖片描述

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

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

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

輸出結(jié)果為真。

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

相關(guān)文章

  • Java實(shí)現(xiàn)24點(diǎn)小游戲

    Java實(shí)現(xiàn)24點(diǎn)小游戲

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)24點(diǎn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • Spring Security中用JWT退出登錄時遇到的坑

    Spring Security中用JWT退出登錄時遇到的坑

    使用了JWT后,每次請求都要攜帶 Bearer Token 并且被專門的過濾器攔截解析之后才能將用戶認(rèn)證信息保存到 SecurityContext 中去,接下來通過本文給大家介紹Spring Security中用JWT退出登錄時遇到的坑,感興趣的朋友一起看看吧
    2021-10-10
  • Quarkus篇入門創(chuàng)建項(xiàng)目搭建debug環(huán)境

    Quarkus篇入門創(chuàng)建項(xiàng)目搭建debug環(huán)境

    這篇文章主要為大家介紹了Quarkus篇入門創(chuàng)建項(xiàng)目搭建debug環(huán)境,先來一套hello?world,來搭建基本的運(yùn)行及調(diào)試環(huán)境吧
    2022-02-02
  • Java常用的時間類以及其轉(zhuǎn)化方式

    Java常用的時間類以及其轉(zhuǎn)化方式

    這篇文章主要介紹了Java常用的時間類以及其轉(zhuǎn)化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • SpringDataJpa創(chuàng)建聯(lián)合索引的實(shí)現(xiàn)

    SpringDataJpa創(chuàng)建聯(lián)合索引的實(shí)現(xiàn)

    這篇文章主要介紹了SpringDataJpa創(chuàng)建聯(lián)合索引的實(shí)現(xiàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java泛型中的通配符舉例詳解

    Java泛型中的通配符舉例詳解

    Java泛型中的通配符是指使用"?"來表示未知類型,可以用于定義泛型類、泛型方法和泛型接口,下面這篇文章主要給大家介紹了關(guān)于Java泛型中通配符的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • Java之Swagger配置掃描接口以及開關(guān)案例講解

    Java之Swagger配置掃描接口以及開關(guān)案例講解

    這篇文章主要介紹了Java之Swagger配置掃描接口以及開關(guān)案例講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Spring Data JPA調(diào)用存儲過程實(shí)例代碼

    Spring Data JPA調(diào)用存儲過程實(shí)例代碼

    本篇文章主要介紹了Spring Data JPA調(diào)用存儲過程實(shí)例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • java 讀取excel文件轉(zhuǎn)換成json格式的實(shí)例代碼

    java 讀取excel文件轉(zhuǎn)換成json格式的實(shí)例代碼

    這篇文章主要介紹了 java 讀取excel文件轉(zhuǎn)換成json格式的實(shí)例代碼,需要的朋友可以參考下
    2018-04-04
  • Java中Comparable和Comparator兩種比較器的區(qū)別詳解

    Java中Comparable和Comparator兩種比較器的區(qū)別詳解

    這篇文章主要介紹了Java中Comparable和Comparator兩種比較器的區(qū)別詳解,Comparable接口將比較代碼嵌入自身類中,像Integer、String等這些基本類型的JAVA封裝類都已經(jīng)實(shí)現(xiàn)了Comparable接口,這些類對象本身就支持和自己比較,需要的朋友可以參考下
    2023-09-09

最新評論

铜梁县| 馆陶县| 休宁县| 兴仁县| 河北区| 乡宁县| 溧阳市| 旬阳县| 尼玛县| 新密市| 老河口市| 东丽区| 澄迈县| 河南省| 和林格尔县| 阳城县| 永靖县| 上饶县| 怀安县| 普定县| 朝阳市| 新龙县| 苏州市| 颍上县| 简阳市| 雅江县| 渭源县| 曲阜市| 宁晋县| 黄梅县| 苗栗县| 虎林市| 肇州县| 关岭| 公安县| 昭苏县| 扶余县| 合山市| 马关县| 长白| 集安市|