類加載機制,雙親委派模型(1)

  通常來講,咱們把java的類加載過程分爲三個主要步驟:加載,連接,初始化。   首先是加載階段,它是java將字節碼數據從不一樣的數據源讀取到JVM中,並映射爲JVM承認的數據結構,並映射爲JVM承認的數據結構(Class對象),這裏的數據源多是各類各樣的形態,如jar文件,class文件,甚至是網絡數據源;若是輸入的數據不是ClassFile的結構,則會拋出ClassFormatError。   第二階段是連接(Linking),這是核心的步驟,簡單說是吧原始的類定義信息平滑地轉化如JVM運行的過程當中。這裏可進一步新氛圍三個步驟:java

  • 驗證(Verification),這是虛擬機安全的重要保障,JVM須要覈驗字節信息是符合Java虛擬機規範的,不然就認爲是VerifyError,這樣就放置了惡意信息或者不合規的信息危害JVM的運行,驗證階段可能觸發更多的class的加載。
  • 準備(Preparation),建立類或者接口中的靜態變量,並初始化靜態變量的初始值。 單這裏的「初始化」和下面的顯式初始化階段是有區別的,側重點在於分配所須要的內存空間,不回去執行更進一步的JVM指令。
  • 解析(Resolution),在這異步會將常量池中的符號引用(symbolic reference)替換爲直接引用。在Java虛擬機規範中,詳細介紹了類、接口、方法和字段等各個方面的解析。   最後是初始化階段(initialization),這一步真正去執行類初始化的代碼邏輯,包括驚呆字段賦值的動做,一級執行類定義總的靜態初始化塊內的邏輯,編譯器在編譯階段就會把這部分邏輯整理好,父類型的初始化邏輯優先於當前類型的邏輯。   在來談談雙親委派模型,簡單說就是當類加載器(Class_Loader)試圖加載某個類型的時候,除非父加載器找不到相應的類型,不然儘可能將整個任務代理給當前加載器的父加載器去作。使用委派模型的目的是避免重複加載Java類型。
package com.wzl.day11;

/**
 * @author wuzhilang
 * @Title: Day11
 * @ProjectName questions
 * @Description: TODO
 * @date 8/27/20196:27 PM
 */

/**
 * 編譯並反編譯一下:
 * 命令:Javac Day11.java
 * Javap –v Day11.class
 */
public class Day11 {
	public static int a = 100;
	public static final int INT_CONSTANT = 1000;
	public static final Integer INTEGER_CONSTANT = Integer.valueOf(10000);
}

運行安全

javac  -encoding UTF-8 Day11.java
Javap –v Day11.class

輸出的結果爲網絡

Classfile /D:/questions/question/src/com/wzl/day11/Day11.class
  Last modified Aug 27, 2019; size 471 bytes
  MD5 checksum c71fc3ab252eb1585d635d591ac61712
  Compiled from "Day11.java"
public class com.wzl.day11.Day11
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#22         // com/wzl/day11/Day11.a:I
   #3 = Methodref          #23.#24        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #4 = Fieldref           #5.#25         // com/wzl/day11/Day11.INTEGER_CONSTANT:Ljava/lang/Integer;
   #5 = Class              #26            // com/wzl/day11/Day11
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               a
   #8 = Utf8               I
   #9 = Utf8               INT_CONSTANT
  #10 = Utf8               ConstantValue
  #11 = Integer            1000
  #12 = Utf8               INTEGER_CONSTANT
  #13 = Utf8               Ljava/lang/Integer;
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               <clinit>
  #19 = Utf8               SourceFile
  #20 = Utf8               Day11.java
  #21 = NameAndType        #14:#15        // "<init>":()V
  #22 = NameAndType        #7:#8          // a:I
  #23 = Class              #28            // java/lang/Integer
  #24 = NameAndType        #29:#30        // valueOf:(I)Ljava/lang/Integer;
  #25 = NameAndType        #12:#13        // INTEGER_CONSTANT:Ljava/lang/Integer;
  #26 = Utf8               com/wzl/day11/Day11
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/Integer
  #29 = Utf8               valueOf
  #30 = Utf8               (I)Ljava/lang/Integer;
{
  public static int a;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public static final int INT_CONSTANT;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1000

  public static final java.lang.Integer INTEGER_CONSTANT;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public com.wzl.day11.Day11();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 16: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        100
         2: putstatic     #2                  // Field a:I
         5: sipush        10000
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: putstatic     #4                  // Field INTEGER_CONSTANT:Ljava/lang/Integer;
        14: return
      LineNumberTable:
        line 17: 0
        line 19: 5
}
SourceFile: "Day11.java"

能夠看出,普通原始類型靜態變量和引用類型(即便是變量),是須要額外調用putstatic等jvm指令的,這些是在顯式初始化階段執行,而不是準備階段調用;而原始類型的變量,則不須要這樣的步驟。數據結構

  • 若是要真正理解雙親委派模型,須要理解java中類加載器的架構和職責,至少要懂具體有哪些內建的類加載器,。
  • 從應用角度,解決某些類加載問題,例如個人java程序啓動較慢,有沒有辦法儘可能減少Java類加載的開銷?
# 指定新的bootclasspath,替換java.*包的內部實現
java -Xbootclasspath:<your_boot_classpath> your_App
# a意味着append,將指定目錄添加到bootclasspath後面
java -Xbootclasspath/a:<your_dir> your_App
# p意味着prepend,將指定目錄添加到bootclasspath前面
java -Xbootclasspath/p:<your_dir> your_App

用法其實很易懂,例如,使用最多見的 「/p」 ,既然是前置,就有機會替換個別基礎類的實現。 咱們通常可使用下面方法獲取類加載器,可是一般的JDK/JRE實現中,擴展類加載器getParent()都只能返回null。架構

public final ClassLoader getParent()
  • 拓展類加載器(Extension or Ext Class-Loader),負責加載咱們最熟悉的classpath的內容。這裏有一個容易混淆的概念,系統(system)類加載器,一般來講,其默認就是JDK內建的應用類加載器,可是它一樣是可能修改的,好比:
java -Djava.system.class.loader=com.yourcorn.YourClassLodader HelloWorld

若是咱們指定了這個桉樹,JDK內建的應用類加載器就會後才能爲定製加載器的父類,這種方式一般用在相似須要改變雙親委派模式的場景。app

具體的操做以下: 一般類加載機制有三個基本特徵:框架

  • 雙親委派模型。但不是全部的類加載都遵照這個模型,有的時候啓動類加載器所加載 的類型,是可能要加載用戶代碼的,好比JDK內部的ServiceProvider/ServiceLoader機制,用戶能夠再標準API框架上,提供本身的實現,JDK也須要提供些默認的參考實現,例如JAVA中JNDI、JDBC、文件系統、Cipher等不少方面,都是利用的這種機制,這種狀況就不會用雙親委派模型去加載,而是利用所謂的上下文加載器。
  • 可見性,子類加載器能夠訪問父加載器的類型,可是反過來是不容許的,否則由於缺乏必要的隔離,咱們就沒有辦法利用類加載器去實現容器的邏輯。
  • 單一性,因爲父加載器的類型對於子加載器是可見的,因此父加載器中加載過的類型,就不會再子加載器中重複加載。可是注意,類加載器「鄰居」間 ,同一類型仍然能夠被加載屢次,由於互相不可見。 -- 未完待續
相關文章
相關標籤/搜索