一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 深入理解Java 類加載全過程

深入理解Java 類加載全過程

2020-08-13 11:43JAVA教程網 Java教程

這篇文章主要介紹了深入理解Java 類加載全過程的相關資料,需要的朋友可以參考下

Java類加載全過程

一個java文件從被加載到被卸載這個生命過程,總共要經歷4個階段:

加載->鏈接(驗證+準備+解析)->初始化(使用前的準備)->使用->卸載

其中加載(除了自定義加載)+鏈接的過程是完全由jvm負責的,什么時候要對類進行初始化工作(加載+鏈接在此之前已經完成了),jvm有嚴格的規定(四種情況):

1.遇到new,getstatic,putstatic,invokestatic這4條字節碼指令時,加入類還沒進行初始化,則馬上對其進行初始化工作。其實就是3種情況:用new實例化一個類時、讀取或者設置類的靜態字段時(不包括被final修飾的靜態字段,因為他們已經被塞進常量池了)、以及執行靜態方法的時候。

2.使用java.lang.reflect.*的方法對類進行反射調用的時候,如果類還沒有進行過初始化,馬上對其進行。

3.初始化一個類的時候,如果他的父親還沒有被初始化,則先去初始化其父親。

4.當jvm啟動時,用戶需要指定一個要執行的主類(包含static void main(String[] args)的那個類),則jvm會先去初始化這個類。

以上4種預處理稱為對一個類進行主動的引用,其余的其他情況,稱為被動引用,都不會觸發類的初始化。下面也舉了些被動引用的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 被動引用情景1
 * 通過子類引用父類的靜態字段,不會導致子類的初始化
 * @author volador
 *
 */
class SuperClass{
  static{
    System.out.println("super class init.");
  }
  public static int value=123;
}
 
class SubClass extends SuperClass{
  static{
    System.out.println("sub class init.");
  }
}
 
public class test{
  public static void main(String[]args){
    System.out.println(SubClass.value);
  }
   
}

輸出結果是:super class init。

?
1
2
3
4
5
6
7
8
9
10
11
/**
 * 被動引用情景2
 * 通過數組引用來引用類,不會觸發此類的初始化
 * @author volador
 *
 */
public class test{
  public static void main(String[] args){
    SuperClass s_list=new SuperClass[10];
  }
}

輸出結果:沒輸出

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 被動引用情景3
 * 常量在編譯階段會被存入調用類的常量池中,本質上并沒有引用到定義常量類類,所以自然不會觸發定義常量的類的初始化
 * @author root
 *
 */
class ConstClass{
  static{
    System.out.println("ConstClass init.");
  }
  public final static String value="hello";
}
 
public class test{
  public static void main(String[] args){
    System.out.println(ConstClass.value);
  }
}

輸出結果:hello(tip:在編譯的時候,ConstClass.value已經被轉變成hello常量放進test類的常量池里面了)

以上是針對類的初始化,接口也要初始化,接口的初始化跟類的初始化有點不同:

上面的代碼都是用static{}來輸出初始化信息的,接口沒法做到,但接口初始化的時候編譯器仍然會給接口生成一個<clinit>()的類構造器,用來初始化接口中的成員變量,這點在類的初始化上也有做到。真正不同的地方在于第三點,類的初始化執行之前要求父類全部都初始化完成了,但接口的初始化貌似對父接口的初始化不怎么感冒,也就是說,子接口初始化的時候并不要求其父接口也完成初始化,只有在真正使用到父接口的時候它才會被初始化(比如引用接口上的常量的時候啦)。

下面分解一下一個類的加載全過程:加載->驗證->準備->解析->初始化

首先是加載:

    這一塊虛擬機要完成3件事:

        1.通過一個類的全限定名來獲取定義此類的二進制字節流。

        2.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

        3.在java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口。

關于第一點,很靈活,很多技術都是在這里切入,因為它并沒有限定二進制流從哪里來:

從class文件來->一般的文件加載

從zip包中來->加載jar中的類

從網絡中來->Applet

..........

相比與加載過程的其他幾個階段,加載階段可控性最強,因為類的加載器可以用系統的,也可以用自己寫的,程序猿可以用自己的方式寫加載器來控制字節流的獲取。

獲取二進制流獲取完成后會按照jvm所需的方式保存在方法區中,同時會在java堆中實例化一個java.lang.Class對象與堆中的數據關聯起來。

加載完成后就要開始對那些字節流進行檢驗了(其實很多步驟是跟上面交叉進行的,比如文件格式驗證):

檢驗的目的:確保class文件的字節流信息符合jvm的口味,不會讓jvm感到不舒服。假如class文件是由純粹的java代碼編譯過來的,自然不會出現類似于數組越界、跳轉到不存在的代碼塊等不健康的問題,因為一旦出現這種現象,編譯器就會拒絕編譯了。但是,跟之前說的一樣,Class文件流不一定是從java源碼編譯過來的,也可能是從網絡或者其他地方過來的,甚至你可以自己用16進制寫,假如jvm不對這些數據進行校驗的話,可能一些有害的字節流會讓jvm完全崩潰。

檢驗主要經歷幾個步驟:文件格式驗證->元數據驗證->字節碼驗證->符號引用驗證

文件格式驗證:驗證字節流是否符合Class文件格式的規范并 驗證其版本是否能被當前的jvm版本所處理。ok沒問題后,字節流就可以進入內存的方法區進行保存了。后面的3個校驗都是在方法區進行的。

元數據驗證:對字節碼描述的信息進行語義化分析,保證其描述的內容符合java語言的語法規范。

字節碼檢驗:最復雜,對方法體的內容進行檢驗,保證其在運行時不會作出什么出格的事來。

符號引用驗證:來驗證一些引用的真實性與可行性,比如代碼里面引了其他類,這里就要去檢測一下那些來究竟是否存在;或者說代碼中訪問了其他類的一些屬性,這里就對那些屬性的可以訪問行進行了檢驗。(這一步將為后面的解析工作打下基礎)

驗證階段很重要,但也不是必要的,假如說一些代碼被反復使用并驗證過可靠性了,實施階段就可以嘗試用-Xverify:none參數來關閉大部分的類驗證措施,以簡短類加載時間。

接著就上面步驟完成后,就會進入準備階段了:

這階段會為類變量(指那些靜態變量)分配內存并設置類比那輛初始值的階段,這些內存在方法區中進行分配。這里要說明一下,這一步只會給那些靜態變量設置一個初始的值,而那些實例變量是在實例化對象時進行分配的。這里的給類變量設初始值跟類變量的賦值有點不同,比如下面:

?
1
public static int value=123;

在這一階段,value的值將會是0,而不是123,因為這個時候還沒開始執行任何java代碼,123還是不可見的,而我們所看到的把123賦值給value的putstatic指令是程序被編譯后存在于<clinit>(),所以,給value賦值為123是在初始化的時候才會執行的。

這里也有個例外:

?
1
public static final int value=123;

這里在準備階段value的值就會初始化為123了。這個是說,在編譯期,javac會為這個特殊的value生成一個ConstantValue屬性,并在準備階段jm就會根據這個ConstantValue的值來為value賦值了。

完成上步后,就要進行解析了。解析好像是對類的字段,方法等東西進行轉換,具體涉及到Class文件的格式內容,并沒深入去了解。

初始化過程是類加載過程的最后一步:

在前面的類加載過程中,除了在加載階段用戶可以通過自定義類加載器參與之外,其他的動作完全有jvm主導,到了初始化這塊,才開始真正執行java里面的代碼。

這一步將會執行一些預操作,注意區分在準備階段,已經為類變量執行過一次系統賦值了。

其實說白了,這一步就是執行程序的<clinit>();方法的過程。下面我們來研究一下<clinit>()方法:

<clinit>()方法叫做類構造器方法,有編譯器自動手機類中的所有類變量的賦值動作和靜態語句塊中的語句合并而成的,置于他們的順序與在源文件中排列的一樣。

<clinit>();方法與類構造方法不一樣,他不需要顯示得調用父類的<clinit>();方法,虛擬機會保證子類的<clinit>();方法在執行前父類的這個方法已經執行完畢了,也就是說,虛擬機中第一個被執行的<clinit>();方法肯定是java.lang.Object類的。

下面來個例子說明一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
static class Parent{
  public static int A=1;
  static{
    A=2;
  }
}
static class Sub extends Parent{
  public static int B=A;
}
public static void main(String[] args){
  System.out.println(Sub.B);
}

首先Sub.B中對靜態數據進行了引用,Sub類要進行初始化了。同時,其父類Parent要先進行初始化動作。Parent初始化后,A=2,所以B=2;上個過程相當于:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static class Parent{
  <clinit>(){
    public static int A=1;
    static{
      A=2;
    }
  }
}
static class Sub extends Parent{
  <clinit>(){ //jvm會先讓父類的該方法執行完在執行這里
  public static int B=A;
  }
}
public static void main(String[] args){
  System.out.println(Sub.B);
}

<clinit>();方法對類跟接口來說不是必須的,加入類或者接口中沒有對類變量進行賦值且沒有靜態代碼塊,<clinit>()方法就不會被編譯器生成。

由于接口里面不能存在static{}這種靜態代碼塊,但仍然可能存在變量初始化時的變量賦值操作,所以接口里面也會生成<clinit>()構造器。但跟類的不同的是,執行子接口的<clinit>();方法前并不需要執行父接口的<clinit>();方法,當父接口中定義的變量被使用時,父接口才會被初始化。

另外,接口的實現類在初始化的時候也一樣不會執行接口的<clinit>();方法。

另外,jvm會保證一個類的<clinit>();方法在多線程環境下能被正確地加鎖同步。<因為初始化只會被執行一次>。

下面用個例子說明一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class DeadLoopClass {
 
  static{
    if(true){
    System.out.println("要被 ["+Thread.currentThread()+"] 初始化了,下面來一個無限循環");
    while(treu){}  
    }
  }
   
  /**
   * @param args
   */
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println("toplaile");
    Runnable run=new Runnable(){
 
      @Override
      public void run() {
        // TODO Auto-generated method stub
        System.out.println("["+Thread.currentThread()+"] 要去實例化那個類了");
        DeadLoopClass d=new DeadLoopClass();
        System.out.println("["+Thread.currentThread()+"] 完成了那個類的初始化工作");
         
      }};
       
      new Thread(run).start();
      new Thread(run).start();
  }
 
}

這里面,運行的時候將會看到阻塞現象。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产成年人视频 | 91精品综合国产在线观看 | 久久天堂成人影院 | 忘忧草研究院一二三 | 美女脱了内裤让男生尿囗 | 贤妻良母电影日本 | 动漫美女被羞羞产奶 | a在线观看欧美在线观看 | 欧美性色老妇人 | 我要看逼 | 欧美贵妇videos办公室360 | 国产一级片免费观看 | 国产欧美日韩在线播放 | 91粉色视频在线导航 | dasd817黑人在线播放 | 亚欧美综合 | 80s在线| 国产精品资源站 | 国色天香高清版 | 高清视频免费 | 我和么公的秘密小说免费 | 美女扒开腿让男生捅 | 亚洲视频在线一区二区 | 免费在线电视 | 日本中文字幕不卡在线一区二区 | 日韩免费一级片 | 毛片免费在线视频 | freefron性中国国产高清 | 日本免费久久久久久久网站 | 高清毛片一区二区三区 | 欧美色成人tv在线播放 | 国产麻豆传媒在线观看 | 91久久国产露脸精品 | 果冻传媒mv在线观看入口免费 | 国产精品美女福利视频免费专区 | 国产成人99久久亚洲综合精品 | 视频在线91| 91免费永久在线地址 | 午夜伦伦电影理论片大片 | 韩国三级在线观看 完整版 韩国三级视频网站 | 黑人巨大初黑人解禁作品 |