溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JVM的藝術(shù)之如何使用類加載器

發(fā)布時(shí)間:2021-10-23 11:46:06 來源:億速云 閱讀:144 作者:iii 欄目:編程語言

這篇文章主要講解了“JVM的藝術(shù)之如何使用類加載器”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JVM的藝術(shù)之如何使用類加載器”吧!

正式介紹線程的上下文類加載器之前需要介紹一些理論性的東東

當(dāng)前類加載器(Current ClassLoader):每一個(gè)類都會(huì)使用自己的類加載器(既加載自身的類加載器)來去加載其它類(指的是所依賴的類),如果ClassX引用了ClassY,那么ClassX的類加載器就會(huì)加載ClassY(前提是ClassY尚未被加載)。

線程上下文類加載器(Context ClassLoader):線程上下文類加載器是從JDK1.2開始引入的,類Thread中的**getContextClassLoader()setContextClassLoader(ClassLoader cl)**分別用來獲取和設(shè)置上下文類加載器。如果沒有通過setContextClassLoader(ClassLoader cl)進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。

Java應(yīng)用運(yùn)行時(shí)初始線程的上下文類加載器是系統(tǒng)類加載器 

為什么使用線程上下文類加載?

為什么使用線程上下文類加載?上篇文章我也簡(jiǎn)單的提到了。線程上下文類加載的設(shè)計(jì)初衷,原因就在于我們JAVA語言的SPI機(jī)制,我又提供了一張圖,希望下面這張圖可以全面的闡述上下文類加載器的含義。JVM的藝術(shù)之如何使用類加載器 

線程上下文類加載器的重要性

我們?cè)谑褂肑DBC操作數(shù)據(jù)庫(kù)時(shí)會(huì)如下進(jìn)行編寫:

Class.forName("com.mysql.driver.Driver");

Connection conn = Driver.getConnection();

Statement st = conn.getStatement();

JDBC是一個(gè)標(biāo)準(zhǔn),這就說明使用到的Connection和Statement都是內(nèi)置在JDK當(dāng)中的標(biāo)準(zhǔn),都是抽象接口,而且是位于rt.jar中,其實(shí)現(xiàn)肯定是由不同的數(shù)據(jù)庫(kù)廠商來實(shí)現(xiàn),那么問題就來了:這些標(biāo)準(zhǔn)都是由根類加載器所加載的,但是具體的實(shí)現(xiàn)是由具體的廠商來做的,那肯定是需要將廠商的jar放到工程的classpath當(dāng)中來進(jìn)行使用,很顯然廠商的這些類是沒辦法由啟動(dòng)類加載器去加載,會(huì)由應(yīng)用類加載器去加載,而根據(jù)**“父類加載器所加載的類或接口是看不到子類加載器所加載的類或接口,而子類加載器所加載的類或接口是能夠看到父類加載器加載的類或接口的”這一原則,那么會(huì)導(dǎo)致這樣一個(gè)局面:比如說java.sql包下面的某個(gè)類會(huì)由啟動(dòng)類加載器去加載,該類有可能會(huì)要訪問具體的實(shí)現(xiàn)類,但具體實(shí)現(xiàn)類是由應(yīng)用類加載器所加載的,java.sql類加載器是根據(jù)看不到具體實(shí)現(xiàn)類加載器所加載的類的,這就是基于雙親委托模型所出現(xiàn)的一個(gè)非常致命的問題,這種問題不僅是在JDBC中會(huì)出現(xiàn),在JNDI、xml解析等SPI(Service Provider Interface)**場(chǎng)景下都會(huì)出現(xiàn)的所以這里總結(jié)一下:父ClassLoader可以使用當(dāng)前線程Thread.currentThread().getContextLoader()所指定的ClassLoader加載的類,這就改變了父ClassLoader不能使用子ClassLoader或者其它沒有直接父子關(guān)系的ClassLoader加載的類的情況,既改變了雙親委托模型。線程上下文類加載器就是當(dāng)前線程的Current ClassLoader。在雙親委托模型下,類加載是由下至上的,既下層的類加載器會(huì)委托上層進(jìn)行加載。但是對(duì)于SPI來說,有些接口是Java核心庫(kù)所提供的,而Java核心庫(kù)是由啟動(dòng)類加載器來加載的,而這些接口的實(shí)現(xiàn)卻來自于不同的jar包(廠商提供)。Java的啟動(dòng)類加載器是不會(huì)加載其它來源的jar包,這樣傳統(tǒng)的雙親委托模型就無法滿足SPI的要求。而通過給當(dāng)前線程設(shè)置上下文類加載器,就可以由設(shè)置的上下文類加載器來實(shí)現(xiàn)對(duì)于接口實(shí)現(xiàn)類的加載。 

下面以JDBC的這種SPI場(chǎng)景用圖來更具體的描述一下:
JVM的藝術(shù)之如何使用類加載器  

很明顯JDBC會(huì)去引用JDBCImpl的具體廠商的實(shí)現(xiàn),而JDBC標(biāo)準(zhǔn)是由根類加載器所加載,那對(duì)于具體實(shí)現(xiàn)廠商的類也會(huì)用根類加載器去加載,而由于它們是處于工程中的classPath當(dāng)中,由系統(tǒng)類加載器去加載,很顯然是沒辦法由根類加載器去加載的,為了解決這個(gè)問題,線程的上下文類加載器就發(fā)揮作用了。

分析:由上面的理論可知:Java應(yīng)用運(yùn)行時(shí)初始線程的上下文類加載器是系統(tǒng)類加載器 

那思考一下:為什么默認(rèn)的線程上下文類加載器就是系統(tǒng)類加載器呢?肯定是在某個(gè)地方給設(shè)置了,其實(shí)它是在Launcher中進(jìn)行設(shè)置的,如下:
JVM的藝術(shù)之如何使用類加載器     
1、線程上下文類加載器的一般使用模式(獲取 - 使用 - 還原)
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();//獲取
        try{
            ClassLoader targetTccl = xxx;//要設(shè)置的上下文類記載器
            Thread.currentThread().setContextClassLoader(targetTccl);//設(shè)置
            myMethod();//使用
        } finally {
            Thread.currentThread().setContextClassLoader(classLoader);//還原
        }
   
2、如果一個(gè)類由類加載器A加載,那么這個(gè)類的依賴類也是由相同的類加載器加載的(如果該依賴類之前沒有被加載過的話),ContextClassLoader的作用就是為破壞Java的類加載委托機(jī)制。 
3、當(dāng)高層提供了統(tǒng)一的接口讓低層來實(shí)現(xiàn),同時(shí)又要在高層加載(或?qū)嵗┑蛯拥念悤r(shí),就必須要通過線程上下文類加載器來幫助高層的ClassLoader找到并加載該類。
Thread.currentThread().getContextClassLoader();//獲取

Thread.currentThread().setContextClassLoader(targetTccl);//設(shè)置
 

至此線程上下文類加載器就介紹到這里。 

類加載的過程

其實(shí)一個(gè)類從加載到使用是要經(jīng)歷很多個(gè)過程的,下面我們來詳細(xì)的說說,一個(gè)類從加載到初始化的這個(gè)過程,然而還有哪些坑不為人知。

下面給出一張圖:

JVM的藝術(shù)之如何使用類加載器  

固定的類加載執(zhí)行順序: 加載  驗(yàn)證  準(zhǔn)備 初始化 卸載 的執(zhí)行順序是一定的  為什么解析過程沒有在這個(gè)執(zhí)行順序中?(接下來分析)

什么時(shí)候觸發(fā)類加載不一定,但是類的初始化如下四種情況就要求一定初始化。 但是初始化之前 就一定會(huì)執(zhí)行 加載 驗(yàn)證 準(zhǔn)備 三個(gè)階段

觸發(fā)類加載的過程(由初始化過程引起的類加載)

1):使用new 關(guān)鍵字   獲取一個(gè)靜態(tài)屬性 設(shè)置一個(gè)靜態(tài)屬性   調(diào)用一個(gè)靜態(tài)方法。

int myValue = SuperClass.value;會(huì)導(dǎo)致父類初始化,但是不會(huì)導(dǎo)致子類初始化

SuperClass.Value = 3 ; 會(huì)導(dǎo)致父類初始化,不會(huì)導(dǎo)致子類初始化。

SubClass.staticMethod(); 先初始化父類 在初始化子類

SubClass sc = new SubClass(); 先初始化父類 再初始化子類

2):使用反射的時(shí)候,若發(fā)現(xiàn)類還沒有初始化,就會(huì)進(jìn)行初始化

Class clazz = Class.forName("com.hnnd.classloader.SubClass");

3):在初始化一個(gè)類的時(shí),若發(fā)現(xiàn)其父類沒有初始化,就會(huì)先初始化父類

SubClass.staticMethod(); 先初始化父類 在初始化子類

4):啟動(dòng)虛擬機(jī)的時(shí)候,需要加載包含main方法的類.

JVM的藝術(shù)之如何使用類加載器  
 class SuperClass{
     public static int value = 5;
     
     static {
      
      System.out.println("Superclass ...... init........");
     }
    }
    
    class SubClass extends SuperClass {
     
     static {
      System.out.println("subClass********************init");
     }
     
     public static void staticMethod(){
      System.out.println("superclass value"+SubClass.value);
     }
    }
 

下面我們對(duì)類的加載、連接、初始化這幾個(gè)過程逐一的解釋:

1:加載

1.1)根據(jù)全類名獲取到對(duì)應(yīng)類的字節(jié)碼流(字節(jié)流的來源 class 文件,網(wǎng)絡(luò)文件,還有反射的Proxygeneraotor.generaotorProxyClass)

1.2)把字節(jié)流中的靜態(tài)數(shù)據(jù)結(jié)構(gòu)加載到方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

1.3)在內(nèi)存中生成java.lang.Class對(duì)象,可以通過該對(duì)象來操作方法區(qū)中的數(shù)據(jù)結(jié)構(gòu)(通過反射)

2:驗(yàn)證

文件格式的驗(yàn)證: 驗(yàn)證class文件開頭的0XCAFFBASE 開頭

驗(yàn)證主次版本號(hào)是否在當(dāng)前的虛擬機(jī)的范圍之類

檢測(cè)jvm不支持的常量類型

元數(shù)據(jù)的校驗(yàn):

驗(yàn)證本類是否有父類

驗(yàn)證是否繼承了不允許繼承的類(final)修飾的類

驗(yàn)證本類不是抽象類的時(shí)候,是否實(shí)現(xiàn)了所有的接口和父類的接口

**字節(jié)碼驗(yàn)證:**驗(yàn)證跳轉(zhuǎn)指令跳轉(zhuǎn)到 方法以外的指令.

驗(yàn)證類型轉(zhuǎn)換是否為有效的, 比如子類對(duì)象賦值父類的引用是可以的,但是把父類對(duì)象賦值給子類引用是危險(xiǎn)的

總而言之:字節(jié)碼驗(yàn)證通過,并不能說明該字節(jié)碼一定沒有問題,但是字節(jié)碼驗(yàn)證不通過。那么該字節(jié)碼文件一定是有問題:。

符號(hào)引用的驗(yàn)證(發(fā)生在解析的過程中):

通過字符串描述的全類名是否能找到對(duì)應(yīng)的類。

指定類中是否包含字段描述符,以及簡(jiǎn)單的字段和方法名稱。

3:準(zhǔn)備:為類變量分配內(nèi)存以及設(shè)置初始值。

比如public static int value = 123;

在準(zhǔn)備的過程中 value=0 而不是123  ,當(dāng)執(zhí)行類的初始化的方法的時(shí)候,value=123

若是一個(gè)靜態(tài)常量

public static final int value = 9; 那么在準(zhǔn)備的過程中value為9.

4:解析 :把符號(hào)引用替換成直接引用

符號(hào)引用分類:

CONSTANT_Class_info 類或者接口的符號(hào)引用

CONSTANT_Fieldref_info 字段的符號(hào)引用

CONSTANT_Methodref_info 方法的符號(hào)引用

CONSTANT_intfaceMethodref_info-   接口中方法的符號(hào)引用

CONSTANT_NameAndType_info  子類或者方法的符號(hào)引用.

CONSTANT_MethodHandle_Info 方法句柄

CONSTANT_InvokeDynamic_Info 動(dòng)態(tài)調(diào)用

直接引用:

指向?qū)ο蟮闹羔?/p>

相對(duì)偏移量

操作句柄

5:初始化:類的初始化時(shí)類加載的最后一步:執(zhí)行類的構(gòu)造器,為所有的類變量進(jìn)行賦值(編譯器生成CLInit<>)

類構(gòu)造器是什么?:類構(gòu)造器是編譯器按照J(rèn)ava源文件總類變量和靜態(tài)代碼塊出現(xiàn)的順序來決定

靜態(tài)語句只能訪問定義在靜態(tài)語句之前的類變量,在其后的靜態(tài)變量能賦值 但是不能訪問。

父類中的靜態(tài)代碼塊優(yōu)先于子類靜態(tài)代碼塊執(zhí)行。

若類中沒有靜態(tài)代碼塊也沒有靜態(tài)類變量的話,那么編譯器就不會(huì)生成 Clint<>類構(gòu)造器的方法。

public class TestClassInit {
 public static void main(String[] args) {
  System.out.println(SubClass.sub_before_v);
 }
}

class SubClass extends SuperClass{
 public static int sub_before_v = 5;
 static {
  sub_before_v = 10;
  System.out.println("subclass init.......");
  sub_after_v=0;
  //報(bào)錯(cuò),static代碼塊中的代碼只能賦值后面的類變量 但是不能訪問。
  sub_before_v = sub_after_v;
 }
 public static int sub_after_v = 10;
}

class SuperClass {
 public static int super_before_v = 5;
 static{
  System.out.println("superclass init......");
 }
 public static int super_after_v = 10;
}

下面我們通過一系列的案例來說驗(yàn)證上面所說的。先做個(gè)小的總結(jié)。

類的初始化需要對(duì)類進(jìn)行主動(dòng)使用,下面總結(jié)了幾點(diǎn),都可以看做是對(duì)類的主動(dòng)使用:

1:創(chuàng)建類的實(shí)例。

2:訪問某個(gè)類或者接口中的靜態(tài)變量,或者對(duì)其賦值。

3:訪問某個(gè)類的靜態(tài)方法。

4:反射。

5:初始化一個(gè)類的子類。

6:包含main方法的類。

7:jdk1.7開始提供動(dòng)態(tài)語言的支持。

除了以上7種情況,都是被動(dòng)使用,都不會(huì)導(dǎo)致類被初始化。

根據(jù)以上結(jié)論,我們來寫幾個(gè)案例,針對(duì)每種情況進(jìn)行一下證明。 

結(jié)論一:

靜態(tài)常量初始化過程是,在jvm連接之后,靜態(tài)常量的初始化,是由調(diào)用這個(gè)靜態(tài)常量方法所在的類的常量池中被保存,此時(shí),被調(diào)用的靜態(tài)常量所在的類的class文件就可以被刪除,即使被刪除,該常量依然有效。調(diào)用某個(gè)類的靜態(tài)常量不能初始化該類。

代碼:

package com.jdyun.jvm001;

public class TestClass03 {

    public static void main(String[] args) {

        System.out.println(Pet1.a);
    }
}

class Pet1{

    public static final int a = 10;

    static {
        System.out.println("我是Pet1,我被初始化了");
    }
}

運(yùn)行結(jié)果:
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=64451:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.TestClass03
10

Process finished with exit code 0
 

從上面這個(gè)案例可知,一個(gè)類調(diào)用另一個(gè)類的常量不會(huì)導(dǎo)致一個(gè)類的初始化。 

結(jié)論二:
  • 此處聲明的靜態(tài)常量,按照之前的理解是靜態(tài)常量被調(diào)用不會(huì)初始化該靜態(tài)常量所在的類
  • 但是此處當(dāng)靜態(tài)常量的值是一個(gè)引用類型的時(shí)候,這個(gè)時(shí)候該靜態(tài)常量所在的類就會(huì)被初始化
  • 故此會(huì)先打印我被初始化了,然后在打印a的隨機(jī)值

代碼:

package com.jdyun.jvm001;

import java.util.UUID;

public class TestClass03 {

    public static void main(String[] args) {
        System.out.println(Pet1.a);

    }
}

class Pet1{
    public static final String a = UUID.randomUUID().toString();

    static{
        System.out.println("我被初始化了");
    }
}

運(yùn)行結(jié)果:
    
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=50237:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.TestClass03
    
我被初始化了
e5b56749-5a97-405f-9fe9-dfe4211bc0ce

Process finished with exit code 0
   
結(jié)論三:

靜態(tài)變量初始化與靜態(tài)常量初始化不同,靜態(tài)變量初始化是在初始化階段被賦予真實(shí)的值比如int a = 2,那么2會(huì)被真正的賦值給a。

如果某個(gè)類調(diào)用了該類的靜態(tài)變量,那么靜態(tài)變量所在的類就會(huì)被視為被主動(dòng)調(diào)用了。那么該類就會(huì)被初始化。

該類如果有靜態(tài)代碼塊兒那么靜態(tài)代碼塊兒的優(yōu)先級(jí)高于靜態(tài)變量。

如果該靜態(tài)變量所在的類中有父類,那么會(huì)優(yōu)先初始化父類。

package com.jdyun.jvm001;

import java.util.Random;
import java.util.UUID;

public class TestClass03 {

    public static void main(String[] args) {

        System.out.println(Dog3.a);

    }
}

class Dog3 extends Pet1{

    public static final  int a = new Random().nextInt();

    static {
        System.out.println("我是Pet1,我是父類,我被最先加載了");
    }

}

class Pet1{

    static{
        System.out.println("我被初始化了");
    }
}

運(yùn)行結(jié)果:
    
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=64951:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.TestClass03
                    
我被初始化了
我是Pet1,我是父類,我被最先加載了
-1203457101

Process finished with exit code 0
    
結(jié)論四:

驗(yàn)證初始化次數(shù),只會(huì)被初始化一次。

package com.jdyun.jvm001;


import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;

public class MyTest02  extends  ClassLoader{

    public static void main(String[] args) throws ClassNotFoundException {
        //1,驗(yàn)證初始化次數(shù)
        for(int i=0;i<50;i++){
            Test01 test01 = new Test01();
        }

    }
}

class Test01{

    static{
        System.out.println("我被初始化了");
    }
}

運(yùn)行結(jié)果:
    
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65340:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.MyTest02
我被初始化了

Process finished with exit code 0
   
結(jié)論五:

接口的初始化,子接口的初始化不會(huì)導(dǎo)致父接口的初始化,如果可以導(dǎo)致父接口的初始化,那么Test01類中的靜態(tài)代碼塊兒就會(huì)被打印。很顯然結(jié)果來看,Test01

中的靜態(tài)代碼塊兒沒有被打印,所以,接口的初始化中,子接口的初始化,不會(huì)導(dǎo)致父接口的初始化。

package com.jdyun.jvm001;


import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;

public class MyTest02  extends  ClassLoader{

    public static void main(String[] args) throws ClassNotFoundException {

        //2,接口初始化,子接口的初始化不會(huì)導(dǎo)致父接口的初始化
        System.out.println(MyChild.b);
       /* System.out.println(MyParent.test01);
        System.out.println(MyChild.test001);*/

        //3,反射初始化類
        //Class.forName("com.jdyun.jvm001.Test01");

        //4,創(chuàng)建數(shù)組不會(huì)導(dǎo)致類的初始化
        //Test01[] test01 = new Test01[1];

        //5,靜態(tài)變量賦值
        //System.out.println(MyChild.b);

        //Class clesses = String.class;


    }
}

class Test01{

    static{
        System.out.println("Test01被初始化了");
    }
}

interface MyParent{

    Test01 test01 = new Test01();

    public static final String a="5";

}

interface MyChild extends MyParent {

    public static  Integer b= UUID.randomUUID().hashCode();


}
"C:\Program  Files\Java\jdk-11.0. 2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=49632:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.MyTest02
-221561202

Process finished with exit code 0
   
結(jié)論六:

創(chuàng)建一個(gè)數(shù)組,不會(huì)導(dǎo)致類的初始化。

package com.jdyun.jvm001;


import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;

public class MyTest02  extends  ClassLoader{

    public static void main(String[] args) throws ClassNotFoundException {

        //4,創(chuàng)建數(shù)組不會(huì)導(dǎo)致類的初始化
        Test01[] test01 = new Test01[1];
    }
}

class Test01{

    static{
        System.out.println("Test01被初始化了");
    }
}

運(yùn)行結(jié)果:

"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=50058:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm001.MyTest02

Process finished with exit code 0
  
結(jié)論七:

此處聲明的靜態(tài)常量,按照之前的理解是靜態(tài)常量被調(diào)用不會(huì)初始化該靜態(tài)常量所在的類 但是此處當(dāng)靜態(tài)常量的值是一個(gè)引用類型的時(shí)候,這個(gè)時(shí)候該靜態(tài)常量所在的類就會(huì)被初始化 故此會(huì)先打印我被初始化了,然后在打印a的隨機(jī)值

package com.jdyun.jvm07;

import java.util.Random;
import java.util.UUID;

/**
 * 此處聲明的靜態(tài)常量,按照之前的理解是靜態(tài)常量被調(diào)用不會(huì)初始化該靜態(tài)常量所在的類
 * 但是此處當(dāng)靜態(tài)常量的值是一個(gè)引用類型的時(shí)候,這個(gè)時(shí)候該靜態(tài)常量所在的類就會(huì)被初始化
 * 故此會(huì)先打印我被初始化了,然后在打印a的隨機(jī)值
 */
public class Test  {

    public static void main(String[] args) {

        System.out.println(Pet.a);

    }
}

class Pet{

    public static final String a = UUID.randomUUID().toString();

    static{

        System.out.println("我被初始化了");
    }
}
運(yùn)行結(jié)果:
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=50995:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm07.Test
我被初始化了
3febaad7-90fe-4d7f-be1c-62b70b9f41cc

Process finished with exit code 0
   
結(jié)論八:

對(duì)子接口靜態(tài)常量調(diào)用時(shí),父接口沒有被加載也并沒有被初始化。當(dāng)我們有兩個(gè)接口,父子接口,然后在子接口中聲明一個(gè)靜態(tài)變量,此時(shí)對(duì)子接口中的靜態(tài)變量進(jìn)行主動(dòng)調(diào)用,此時(shí)父接口沒有被初始化,也沒有被加載。(刪除父接口中的class)

package com.jdyun.jvm8;

import java.util.Random;

public class Test {

    public static void main(String[] args) {
        System.out.println(MyChild.b);
    }

}

interface MyParent{

    public static final String a="5";

}

interface MyChild extends MyParent{

    public static  Integer b= 1;
}

運(yùn)行結(jié)果:
    
"C:\Program Files\Java\jdk-11.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=51297:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath G:\jdyun-jvm\out\production\jdyun-jvm com.jdyun.jvm8.Test
1

Process finished with exit code 0
   
結(jié)論九:

接口中的變量賦予引用初始值會(huì)初始化子接口。

public class Test {

    public static void main(String[] args) {
        System.out.println(MyChild.b);
    }

}

interface MyParent{

    public static String a=5;

}

interface MyChild extends  MyParent{

    Integer b= new Random().nextInt(2);
}

感謝各位的閱讀,以上就是“JVM的藝術(shù)之如何使用類加載器”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)JVM的藝術(shù)之如何使用類加載器這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

jvm
AI