国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

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

2019-11-26 14:52:38
字體:
供稿:網(wǎng)友

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)變量將原有的值。

 

  一個(gè)比較奇怪的例子:

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);  }}

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

  這是因?yàn)槌跏蓟Z句是按照順序來執(zhí)行的。

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

 

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

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

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

 

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

  1.主動使用

  2.被動使用

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

 

  主動使用的六種情況:

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

new Test();  

  2.訪問某個(gè)類或接口的靜態(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.初始化一個(gè)類的子類

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

  

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

java com.mengdd.Test

 

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

 

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

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

    在初始化一個(gè)接口時(shí),并不會先初始化它的父接口。

  因此,一個(gè)父接口并不會因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)類的初始化而初始化,只有當(dāng)程序首次使用特定接口的靜態(tài)變量時(shí),才會導(dǎo)致該接口的初始化。

 

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

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

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

例子代碼:

package com.mengdd.classloader;import java.util.Random;class FinalTest1 {  public static final int x = 6 / 3; // 編譯時(shí)期已經(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);// 只有運(yùn)行時(shí)才能得到值  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)方法確實(shí)在當(dāng)前類或當(dāng)前接口中定義時(shí),才可以認(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類沒有初始化    // 這是因?yàn)橹鲃邮褂玫淖兞亢头椒ǘ际嵌x在Parent類中的  }}

 

ClassLoader類
  調(diào)用ClassLoader類的loadClass()方法加載一個(gè)類,并不是對類的主動使用,不會導(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方法加載一個(gè)類,并不是對類的主動使用,不會導(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所指定的目錄中加載類庫。

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

 

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

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

  它從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或者從JDK的安裝目錄的jre/lib/ext子目錄(擴(kuò)展目錄)下加載類庫,如果把用戶創(chuàng)建的JAR文件放在這個(gè)目錄下,也會自動由擴(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)系,子加載器不一定繼承了父加載器(其實(shí)是組合的關(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ī)自帶的根類加載器以外,其余的類加載器都有且只有一個(gè)父加載器,各個(gè)加載器按照父子關(guān)系形成了樹形結(jié)構(gòu)。

 

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

 

  說明具體過程的一個(gè)例子:

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é)下來就是:

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

 

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

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

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

 

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

  一對父子加載器可能是同一個(gè)加載器類的兩個(gè)實(shí)例,也可能不是。

  在子加載器對象中包裝了一個(gè)父加載器對象。

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

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

 

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

  因?yàn)樵诖藱C(jī)制下,用戶自定義的類加載器不可能加載應(yīng)該由父加載器加載的可靠類,從而防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼。

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

 

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

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

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

運(yùn)行時(shí)包
  由同一類加載器加載的屬于相同包的類組成了運(yùn)行時(shí)包。

  決定兩個(gè)類是不是屬于同一個(gè)運(yùn)行時(shí)包,不僅要看它們的包名是否相同,還要看定義類加載器是否相同。

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

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

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

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 前郭尔| 盐津县| 琼中| 涟源市| 白朗县| 卢氏县| 万荣县| 十堰市| 泊头市| 上虞市| 鹤壁市| 汝城县| 西充县| 札达县| 廉江市| 揭东县| 西乌珠穆沁旗| 山阴县| 阜城县| 德钦县| 商城县| 鄱阳县| 大名县| 伊通| 濮阳县| 布尔津县| 苗栗县| 南昌县| 镇沅| 兴安县| 广河县| 陈巴尔虎旗| 铁岭县| 南召县| 灌云县| 乌拉特前旗| 安国市| 名山县| 竹山县| 白朗县| 密云县|