欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析Java虛擬機(jī)中類的初始化及加載器的父委托機(jī)制

 更新時間:2015年11月06日 09:01:25   作者:圣騎士Wind的博客  
這篇文章主要介紹了Java虛擬機(jī)中類的初始化及加載器的父委托機(jī)制,包括命名空間等深層次的知識點講解,需要的朋友可以參考下

201511685745613.png (365×202)

類的初始化
  在初始化階段,Java虛擬機(jī)執(zhí)行類的初始化語句,為類的靜態(tài)變量賦予初始值。

  在程序中,靜態(tài)變量的初始化有兩種途徑:

  1.在靜態(tài)變量的聲明處進(jìn)行初始化;

  2.在靜態(tài)代碼塊中進(jìn)行初始化。

  沒有經(jīng)過顯式初始化的靜態(tài)變量將原有的值。

 

  一個比較奇怪的例子:

package com.mengdd.classloader;

class Singleton {

  // private static Singleton mInstance = new Singleton();// 位置1
  // 位置1輸出:
  // counter1: 1
  // counter2: 0
  public static int counter1;
  public static int counter2 = 0;

  private static Singleton mInstance = new Singleton();// 位置2

  // 位置2輸出:
  // counter1: 1
  // counter2: 1

  private Singleton() {
    counter1++;
    counter2++;
  }

  public static Singleton getInstantce() {
    return mInstance;
  }
}

public class Test1 {

  public static void main(String[] args) {

    Singleton singleton = Singleton.getInstantce();
    System.out.println("counter1: " + Singleton.counter1);
    System.out.println("counter2: " + Singleton.counter2);
  }
}

可見將生成對象的語句放在兩個位置,輸出是不一樣的(相應(yīng)位置的輸出已在程序注釋中標(biāo)明)。

  這是因為初始化語句是按照順序來執(zhí)行的。

  靜態(tài)變量的聲明語句,以及靜態(tài)代碼塊都被看做類的初始化語句,Java虛擬機(jī)會按照初始化語句在類文件中的先后順序來依次執(zhí)行它們。

 

類的初始化步驟
  1.假如這個類還沒有被加載和連接,那就先進(jìn)行加載和連接。

  2.假如類存在直接的父類,并且這個父類還沒有被初始化,那就先初始化直接的父類。

  3.假如類中存在初始化語句,那就依次執(zhí)行這些初始化語句。

 

類的初始化時機(jī)
  Java程序?qū)︻惖氖褂梅绞娇梢苑譃閮煞N:

  1.主動使用

  2.被動使用

  所有的Java虛擬機(jī)實現(xiàn)必須在每個類或接口被Java程序首次主動使用時才初始化它們。

 

  主動使用的六種情況:

  1.創(chuàng)建類的實例。

new Test();
  

  2.訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值。

int b = Test.a;
Test.a = b;

  

  3.調(diào)用類的靜態(tài)方法

Test.doSomething();

  

  4.反射

Class.forName(“com.mengdd.Test”);

  

  5.初始化一個類的子類

class Parent{
}
class Child extends Parent{
   public static int a = 3;
}
Child.a = 4;

  

  6.Java虛擬機(jī)啟動時被標(biāo)明為啟動類的類

java com.mengdd.Test

 

   除了以上六種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導(dǎo)致類的初始化。

 

接口的特殊性
  當(dāng)Java虛擬機(jī)初始化一個類時,要求它的所有父類都已經(jīng)被初始化,但是這條規(guī)則并不適用于接口。

    在初始化一個類時,并不會先初始化它所實現(xiàn)的接口。

    在初始化一個接口時,并不會先初始化它的父接口。

  因此,一個父接口并不會因為它的子接口或者實現(xiàn)類的初始化而初始化,只有當(dāng)程序首次使用特定接口的靜態(tài)變量時,才會導(dǎo)致該接口的初始化。

 

final類型的靜態(tài)變量
  final類型的靜態(tài)變量是編譯時常量還是變量,會影響初始化語句塊的執(zhí)行。

  如果一個靜態(tài)變量的值是一個編譯時的常量,就不會對類型進(jìn)行初始化(類的static塊不執(zhí)行);

    如果一個靜態(tài)變量的值是一個非編譯時的常量,即只有運行時會有確定的初始化值,則就會對這個類型進(jìn)行初始化(類的static塊執(zhí)行)。

例子代碼:

package com.mengdd.classloader;

import java.util.Random;

class FinalTest1 {
  public static final int x = 6 / 3; // 編譯時期已經(jīng)可知其值為2,是常量
  // 類型不需要進(jìn)行初始化
  static {
    System.out.println("static block in FinalTest1");
    // 此段語句不會被執(zhí)行,即無輸出
  }
}

class FinalTest2 {
  public static final int x = new Random().nextInt(100);// 只有運行時才能得到值
  static {
    System.out.println("static block in FinalTest2");
    // 會進(jìn)行類的初始化,即靜態(tài)語句塊會執(zhí)行,有輸出
  }
}

public class InitTest {

  public static void main(String[] args) {
    System.out.println("FinalTest1: " + FinalTest1.x);
    System.out.println("FinalTest2: " + FinalTest2.x);
  }
}

主動使用的歸屬明確性
  只有當(dāng)程序訪問的靜態(tài)變量或靜態(tài)方法確實在當(dāng)前類或當(dāng)前接口中定義時,才可以認(rèn)為是對類或接口的主動使用。

package com.mengdd.classloader;

class Parent {
  static int a = 3;

  static {
    System.out.println("Parent static block");
  }

  static void doSomething() {
    System.out.println("do something");
  }
}

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

public class ParentTest {

  public static void main(String[] args) {

    System.out.println("Child.a: " + Child.a);
    Child.doSomething();

    // Child類的靜態(tài)代碼塊沒有執(zhí)行,說明Child類沒有初始化
    // 這是因為主動使用的變量和方法都是定義在Parent類中的
  }
}

 

ClassLoader類
  調(diào)用ClassLoader類的loadClass()方法加載一個類,并不是對類的主動使用,不會導(dǎo)致類的初始化。

 package com.mengdd.classloader;

class CL {

  static {
    System.out.println("static block in CL");
  }
}

public class ClassLoaderInitTest {

  public static void main(String[] args) throws Exception {
    ClassLoader loader = ClassLoader.getSystemClassLoader();
    Class<?> clazz = loader.loadClass("com.mengdd.classloader.CL");
    // loadClass方法加載一個類,并不是對類的主動使用,不會導(dǎo)致類的初始化

    System.out.println("----------------");
    clazz = Class.forName("com.mengdd.classloader.CL");
  }

}

類加載器的父委托機(jī)制

類加載器
  類加載器用來把類加載到Java虛擬機(jī)中。

 

類加載器的類型
  有兩種類型的類加載器:

  1.JVM自帶的加載器:

    根類加載器(Bootstrap)

    擴(kuò)展類加載器(Extension)

    系統(tǒng)類加載器(System)

  2.用戶自定義的類加載器:

    java.lang.ClassLoader的子類,用戶可以定制類的加載方式。

 

JVM自帶的加載器
  Java虛擬機(jī)自帶了以下幾種加載器。

  1.根(Bootstrap)類加載器:

  該加載器沒有父加載器。

  它負(fù)責(zé)加載虛擬機(jī)的核心類庫,如java.lang.*等。

  根類加載器從系統(tǒng)屬性sun.boot.class.path所指定的目錄中加載類庫。

  根類加載器的實現(xiàn)依賴于底層操作系統(tǒng),屬于虛擬機(jī)的實現(xiàn)的一部分,它并沒有繼承java.lang.ClassLoader類,它是用C++寫的。

 

  2.擴(kuò)展(Extension)類加載器:

  它的父加載器為根類加載器。

  它從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或者從JDK的安裝目錄的jre\lib\ext子目錄(擴(kuò)展目錄)下加載類庫,如果把用戶創(chuàng)建的JAR文件放在這個目錄下,也會自動由擴(kuò)展類加載器加載。

  擴(kuò)展類加載器是純Java類,是java.lang.ClassLoader類的子類。

 

  3.系統(tǒng)(System)類加載器:

  也稱為應(yīng)用類加載器,它的父加載器為擴(kuò)展類加載器。

  它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類,它是用戶自定義的類加載器的默認(rèn)父加載器。

  系統(tǒng)類加載器是純Java類,是java.lang.ClassLoader類的子類。

 

  注意:這里的父加載器概念并不是指類的繼承關(guān)系,子加載器不一定繼承了父加載器(其實是組合的關(guān)系)。

 

用戶自定義類加載器
  除了以上虛擬機(jī)自帶的類加載器以外,用戶還可以定制自己的類加載器(User-defined Class Loader)。

  Java提供了抽象類java.lang.ClassLoader,所有用戶自定義的類加載器都應(yīng)該繼承ClassLoader類。

201511685814089.png (194×344)

類加載的父委托機(jī)制
  從JDK 1.2版本開始,類的加載過程采用父親委托機(jī)制,這種機(jī)制能更好地保證Java平臺的安全。

  在父委托機(jī)制中,除了Java虛擬機(jī)自帶的根類加載器以外,其余的類加載器都有且只有一個父加載器,各個加載器按照父子關(guān)系形成了樹形結(jié)構(gòu)。

 

  當(dāng)Java程序請求加載器loader1加載Sample類時,loader1首先委托自己的父加載器去加載Sample類,若父加載器能加載,則由父加載器完成加載任務(wù),否則才由loader1本身加載Sample類。

 

  說明具體過程的一個例子:

201511685839494.png (360×477)

loader2首先從自己的命名空間中查找Sample類是否已經(jīng)被加載,如果已經(jīng)加載,就直接返回代表Sample類的Class對象的引用。

  如果Sample類還沒有被加載,loader2首先請求loader1代為加載,loader1再請求系統(tǒng)類加載器代為加載,系統(tǒng)類加載器再請求擴(kuò)展類加載器代為加載,擴(kuò)展類加載器再請求根類加載器代為加載。

  若根類加載器和擴(kuò)展類加載器都不能加載,則系統(tǒng)類加載器嘗試加載,若能加載成功,則將Sample類所對應(yīng)的Class對象的引用返回給loader1,loader1再返回給loader2,從而成功將Sample類加載進(jìn)虛擬機(jī)。

  若系統(tǒng)加載器不能加載Sample類,則loader1嘗試加載Sample類,若loader1也不能成功加載,則loader2嘗試加載。

  若所有的父加載器及l(fā)oader2本身都不能加載,則拋出ClassNotFoundException異常。

  總結(jié)下來就是:

  每個加載器都優(yōu)先嘗試用父類加載,若父類不能加載則自己嘗試加載;若成功則返回Class對象給子類,若失敗則告訴子類讓子類自己加載。所有都失敗則拋出異常。

 

定義類加載器和初始類加載器
  若有一個類加載器能成功加載Sample類,那么這個類加載器被稱為定義類加載器。

  所有能成功返回Class對象的引用的類加載器(包括定義類加載器,即包括定義類加載器和它下面的所有子加載器)都被稱為初始類加載器。

  假設(shè)loader1實際加載了Sample類,則loader1為Sample類的定義類加載器,loader2和loader1為Sample類的初始類加載器。

 

父子關(guān)系
  需要指出的是,加載器之間的父子關(guān)系實際上指的是加載器對象之間的包裝關(guān)系,而不是類之間的繼承關(guān)系。

  一對父子加載器可能是同一個加載器類的兩個實例,也可能不是。

  在子加載器對象中包裝了一個父加載器對象。

  例如loader1和loader2都是MyClassLoader類的實例,并且loader2包裝了loader1,loader1是loader2的父加載器。

  當(dāng)生成一個自定義的類加載器實例時,如果沒有指定它的父加載器(ClassLoader構(gòu)造方法無參數(shù)),那么系統(tǒng)類加載器就將成為該類加載器的父加載器。

 

父委托機(jī)制優(yōu)點
  父親委托機(jī)制的優(yōu)點是能夠提高軟件系統(tǒng)的安全性。

  因為在此機(jī)制下,用戶自定義的類加載器不可能加載應(yīng)該由父加載器加載的可靠類,從而防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼。

  例如,java.lang.Object類總是由根類加載器加載,其他任何用戶自定義的類加載器都不可能加載含有惡意代碼的java.lang.Object類。

 

命名空間
  每個類加載器都有自己的命名空間,命名空間由該加載器及所有父加載器所加載的類組成。

  在同一個命名空間中,不會出現(xiàn)類的完整名字(包括類的包名)相同的兩個類。

  在不同的命名空間中,有可能會出現(xiàn)類的完整名字(包括類的包名)相同的兩個類。

運行時包
  由同一類加載器加載的屬于相同包的類組成了運行時包。

  決定兩個類是不是屬于同一個運行時包,不僅要看它們的包名是否相同,還要看定義類加載器是否相同。

  只有屬于同一運行時包的類才能互相訪問包可見(即默認(rèn)訪問級別)的類和類成員。

  這樣的限制能避免用戶自定義的類冒充核心類庫的類,去訪問核心類庫的包可見成員。

  假設(shè)用戶自己定義了一個類java.lang.Spy,并由用戶自定義的類加載器加載,由于java.lang.Spy和核心類庫java.lang.*由不同的類加載器加載,它們屬于不同的運行時包,所以java.lang.Spy不能訪問核心類庫java.lang包中的包可見成員。

相關(guān)文章

  • Java正則校驗密碼至少包含字母數(shù)字特殊符號中的2種實例代碼

    Java正則校驗密碼至少包含字母數(shù)字特殊符號中的2種實例代碼

    正則表達(dá)式驗證密碼功能在項目中經(jīng)常被使用到,但是很多朋友還是不大會使用密碼正則表達(dá)式進(jìn)行驗證,下面這篇文章主要給大家介紹了關(guān)于Java正則校驗密碼至少包含字母數(shù)字特殊符號中2種的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • java控制臺版實現(xiàn)五子棋游戲

    java控制臺版實現(xiàn)五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了java控制臺版實現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Spring框架+jdbcTemplate實現(xiàn)增刪改查功能

    Spring框架+jdbcTemplate實現(xiàn)增刪改查功能

    這篇文章主要介紹了Spring框架+jdbcTemplate實現(xiàn)增刪改查功能,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • xxl-job的部署及springboot集成使用示例詳解

    xxl-job的部署及springboot集成使用示例詳解

    XXL-Job是一個分布式任務(wù)調(diào)度平臺,可進(jìn)行任務(wù)調(diào)度、管理和監(jiān)控,并提供任務(wù)分片、失敗重試、動態(tài)分配等功能,這篇文章主要介紹了xxl-job的部署及springboot集成使用,需要的朋友可以參考下
    2023-06-06
  • Mybatis特殊字符轉(zhuǎn)義查詢實現(xiàn)

    Mybatis特殊字符轉(zhuǎn)義查詢實現(xiàn)

    本文主要介紹了Mybatis特殊字符轉(zhuǎn)義查詢實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • 在SpringBoot中注入RedisTemplate實例異常的解決方案

    在SpringBoot中注入RedisTemplate實例異常的解決方案

    這篇文章主要介紹了在SpringBoot中注入RedisTemplate實例異常的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 區(qū)分Java的方法覆蓋與變量覆蓋

    區(qū)分Java的方法覆蓋與變量覆蓋

    作為初學(xué)者2個比較容易出錯的定義,方法覆蓋和變量覆蓋。下面我們一起來看看作者如何去探討Java的方法覆蓋和變量覆蓋。
    2015-09-09
  • IDEA快速部署Spring?Boot?項目到Docker的實現(xiàn)方法

    IDEA快速部署Spring?Boot?項目到Docker的實現(xiàn)方法

    本文主要介紹了IDEA快速部署Spring?Boot?項目到Docker的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • java之多線程搶火車票的實現(xiàn)示例

    java之多線程搶火車票的實現(xiàn)示例

    生活中有很多多線程的案例,購票就是一個很常見的問題,本文主要介紹了java之多線程搶火車票的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • 使用Spring開啟@Async異步方式(javaconfig配置)

    使用Spring開啟@Async異步方式(javaconfig配置)

    這篇文章主要介紹了使用Spring開啟@Async異步方式(javaconfig配置),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08

最新評論