国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JVM 的類初始化機(jī)制

20171112 / 2660人閱讀

摘要:當(dāng)你在程序中對(duì)象時(shí),有沒有考慮過是如何把靜態(tài)的字節(jié)碼轉(zhuǎn)化為運(yùn)行時(shí)對(duì)象的呢,這個(gè)問題看似簡(jiǎn)單,但清楚的同學(xué)相信也不會(huì)太多,這篇文章首先介紹類初始化的機(jī)制,然后給出幾個(gè)易出錯(cuò)的實(shí)例來(lái)分析,幫助大家更好理解這個(gè)知識(shí)點(diǎn)。

當(dāng)你在 Java 程序中new對(duì)象時(shí),有沒有考慮過 JVM 是如何把靜態(tài)的字節(jié)碼(byte code)轉(zhuǎn)化為運(yùn)行時(shí)對(duì)象的呢,這個(gè)問題看似簡(jiǎn)單,但清楚的同學(xué)相信也不會(huì)太多,這篇文章首先介紹 JVM 類初始化的機(jī)制,然后給出幾個(gè)易出錯(cuò)的實(shí)例來(lái)分析,幫助大家更好理解這個(gè)知識(shí)點(diǎn)。

Loading, Linking, and Initialization

JVM 將字節(jié)碼轉(zhuǎn)化為運(yùn)行時(shí)對(duì)象分為三個(gè)階段,分別是:loading 、Linking、initialization。

下面分別介紹這三個(gè)過程:

Loading

Loading 過程主要工作是由ClassLoader完成。該過程具體包括三件事:

根據(jù)類的全名,生成一份二進(jìn)制字節(jié)碼來(lái)表示該類

將二進(jìn)制的字節(jié)碼解析成方法區(qū)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)

最后生成一 Class 對(duì)象的實(shí)例來(lái)表示該類

JVM 中除了最頂層的Boostrap ClassLoader是用 C/C++ 實(shí)現(xiàn)外,其余類加載器均由 Java 實(shí)現(xiàn),我們可以用getClassLoader方法來(lái)獲取當(dāng)前類的類加載器:

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println(ClassLoaderDemo.class.getClassLoader());
    }
}

# sun.misc.Launcher$AppClassLoader@30a4effe
# AppClassLoader 也就是上圖中的 System Class Loader

此外,我們?cè)趩?dòng)java傳入-verbose:class來(lái)查看加載的類有那些。

java -verbose:class ClassLoaderDemo

[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]

....
....

[Loaded java.security.BasicPermissionCollection from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded ClassLoaderDemo from file:/Users/liujiacai/codes/IdeaProjects/mysql-test/target/classes/]
[Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
sun.misc.Launcher$AppClassLoader@2a139a55
[Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
Linking Verification

Verification 主要是保證類符合 Java 語(yǔ)法規(guī)范,確保不會(huì)影響 JVM 的運(yùn)行。包括但不限于以下事項(xiàng):

bytecode 的完整性(integrity)

檢查final類沒有被繼承,final方法沒有被覆蓋

確保沒有不兼容的方法簽名

Preparation

在一個(gè)類已經(jīng)被load并且通過verification后,就進(jìn)入到preparation階段。在這個(gè)階段,JVM 會(huì)為類的成員變量分配內(nèi)存空間并且賦予默認(rèn)初始值,需要注意的是這個(gè)階段不會(huì)執(zhí)行任何代碼,而只是根據(jù)變量類型決定初始值。如果不進(jìn)行默認(rèn)初始化,分配的空間的值是隨機(jī)的,有點(diǎn)類型c語(yǔ)言中的野指針問題。

Type    Initial Value
int    0
long    0L
short    (short) 0
char    "u0000"
byte    (byte) 0
boolean    false
reference    null
float    0.0f
double    0.0d

在這個(gè)階段,JVM 也可能會(huì)為有助于提高程序性能的數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存,常見的一個(gè)稱為method table的數(shù)據(jù)結(jié)構(gòu),它包含了指向所有類方法(也包括也從父類繼承的方法)的指針,這樣再調(diào)用父類方法時(shí)就不用再去搜索了。

Resolution

Resolution 階段主要工作是確認(rèn)類、接口、屬性和方法在類run-time constant pool的位置,并且把這些符號(hào)引用(symbolic references)替換為直接引用(direct references)。

locating classes, interfaces, fields, and methods referenced symbolically from a type"s constant pool, and replacing those symbolic references with direct references.

這個(gè)過程不是必須的,也可以發(fā)生在第一次使用某個(gè)符號(hào)引用時(shí)。

Initialization

經(jīng)過了上面的loadlink后,第一次 主動(dòng)調(diào)用某類的最后一步是Initialization,這個(gè)過程會(huì)去按照代碼書寫順序進(jìn)行初始化,這個(gè)階段會(huì)去真正執(zhí)行代碼,注意包括:代碼塊(static與static)、構(gòu)造函數(shù)、變量顯式賦值。如果一個(gè)類有父類,會(huì)先去執(zhí)行父類的initialization階段,然后在執(zhí)行自己的。

上面這段話有兩個(gè)關(guān)鍵詞:第一次主動(dòng)調(diào)用第一次是說(shuō)只在第一次時(shí)才會(huì)有初始化過程,以后就不需要了,可以理解為每個(gè)類有且僅有一次初始化的機(jī)會(huì)。那么什么是主動(dòng)調(diào)用呢?
JVM 規(guī)定了以下六種情況為主動(dòng)調(diào)用,其余的皆為被動(dòng)調(diào)用

一個(gè)類的實(shí)例被創(chuàng)建(new操作、反射、cloning,反序列化)

調(diào)用類的static方法

使用或?qū)︻?接口的static屬性進(jìn)行賦值時(shí)(這不包括final的與在編譯期確定的常量表達(dá)式)

當(dāng)調(diào)用 API 中的某些反射方法時(shí)

子類被初始化

被設(shè)定為 JVM 啟動(dòng)時(shí)的啟動(dòng)類(具有main方法的類)

本文后面會(huì)給出一個(gè)示例用于說(shuō)明主動(dòng)調(diào)用被動(dòng)調(diào)用區(qū)別。

在這個(gè)階段,執(zhí)行代碼的順序遵循以下兩個(gè)原則:

有static先初始化static,然后是非static的

顯式初始化,構(gòu)造塊初始化,最后調(diào)用構(gòu)造函數(shù)進(jìn)行初始化

示例 屬性在不同時(shí)期的賦值
class Singleton {

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

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

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

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

public class InitDemo {

    public static void main(String[] args) {

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

當(dāng)mInstance在位置1時(shí),打印出

counter1: 1
counter2: 0

當(dāng)mInstance在位置2時(shí),打印出

counter1: 1
counter2: 1

Singleton中的三個(gè)屬性在Preparation階段會(huì)根據(jù)類型賦予默認(rèn)值,在Initialization階段會(huì)根據(jù)顯示賦值的表達(dá)式再次進(jìn)行賦值(按順序自上而下執(zhí)行)。根據(jù)這兩點(diǎn),就不難理解上面的結(jié)果了。

主動(dòng)調(diào)用 vs. 被動(dòng)調(diào)用
class NewParent {

    static int hoursOfSleep = (int) (Math.random() * 3.0);

    static {
        System.out.println("NewParent was initialized.");
    }
}

class NewbornBaby extends NewParent {

    static int hoursOfCrying = 6 + (int) (Math.random() * 2.0);

    static {
        System.out.println("NewbornBaby was initialized.");
    }
}

public class ActiveUsageDemo {

    // Invoking main() is an active use of ActiveUsageDemo
    public static void main(String[] args) {

        // Using hoursOfSleep is an active use of NewParent,
        // but a passive use of NewbornBaby
        System.out.println(NewbornBaby.hoursOfSleep);
    }

    static {
        System.out.println("ActiveUsageDemo was initialized.");
    }
}

上面的程序最終輸出:

ActiveUsageDemo was initialized.
NewParent was initialized.
1

之所以沒有輸出NewbornBaby was initialized.是因?yàn)闆]有主動(dòng)去調(diào)用NewbornBaby,如果把打印的內(nèi)容改為NewbornBaby.hoursOfCrying 那么這時(shí)就是主動(dòng)調(diào)用NewbornBaby了,相應(yīng)的語(yǔ)句也會(huì)打印出來(lái)。

首次主動(dòng)調(diào)用才會(huì)初始化
public class Alibaba {

    public static int k = 0;
    public static Alibaba t1 = new Alibaba("t1");
    public static Alibaba t2 = new Alibaba("t2");
    public static int i = print("i");
    public static int n = 99;
    private int a = 0;
    public int j = print("j");
    {
        print("構(gòu)造塊");
    }
    static {
        print("靜態(tài)塊");
    }

    public Alibaba(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++i;
        ++n;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++n;
        return ++i;
    }

    public static void main(String args[]) {
        Alibaba t = new Alibaba("init");
    }
}

上面這個(gè)例子是阿里巴巴在14年的校招附加題,我當(dāng)時(shí)看到這個(gè)題,就覺得與阿里無(wú)緣了。囧

1:j   i=0    n=0
2:構(gòu)造塊   i=1    n=1
3:t1   i=2    n=2
4:j   i=3    n=3
5:構(gòu)造塊   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:靜態(tài)塊   i=7    n=99
9:j   i=8    n=100
10:構(gòu)造塊   i=9    n=101
11:init   i=10    n=102

上面是程序的輸出結(jié)果,下面我來(lái)一行行分析之。

由于Alibaba是 JVM 的啟動(dòng)類,屬于主動(dòng)調(diào)用,所以會(huì)依此進(jìn)行 loading、linking、initialization 三個(gè)過程。

經(jīng)過 loading與 linking 階段后,所有的屬性都有了默認(rèn)值,然后進(jìn)入最后的 initialization 階段。

在 initialization 階段,先對(duì) static 屬性賦值,然后在非 static 的。k 第一個(gè)顯式賦值為 0 。

接下來(lái)是t1屬性,由于這時(shí)Alibaba這個(gè)類已經(jīng)處于 initialization 階段,static 變量無(wú)需再次初始化了,所以忽略 static 屬性的賦值,只對(duì)非 static 的屬性進(jìn)行賦值,所有有了開始的:

    1:j   i=0    n=0
    2:構(gòu)造塊   i=1    n=1
    3:t1   i=2    n=2

接著對(duì)t2進(jìn)行賦值,過程與t1相同

    4:j   i=3    n=3
    5:構(gòu)造塊   i=4    n=4
    6:t2   i=5    n=5

之后到了 static 的 in

    7:i   i=6    n=6

到現(xiàn)在為止,所有的static的成員變量已經(jīng)賦值完成,接下來(lái)就到了 static 代碼塊

    8:靜態(tài)塊   i=7    n=99

至此,所有的 static 部分賦值完畢,接下來(lái)是非 static 的 j

    9:j   i=8    n=100

所有屬性都賦值完畢,最后是構(gòu)造塊與構(gòu)造函數(shù)

    10:構(gòu)造塊   i=9    n=101
    11:init   i=10    n=102

經(jīng)過上面這9步,Alibaba這個(gè)類的初始化過程就算完成了。這里面比較容易出錯(cuò)的是第3步,認(rèn)為會(huì)再次初始化 static 變量或代碼塊。而實(shí)際上是沒必要,否則會(huì)出現(xiàn)多次初始化的情況。

希望大家能多思考思考這個(gè)例子的結(jié)果,加深這三個(gè)過程的理解。

總結(jié)

經(jīng)過最后這三個(gè)例子,相信大家對(duì) JVM 對(duì)類加載機(jī)制都有了更深的理解,如果大家還是有疑問,歡迎留意討論。

參考

Java Virtual Machine Specification Chapter 5

Chapter 7 of Inside the Java Virtual Machine

JVM Internals

What kind of method is Constructor, static or non static?

Understanding the Java ClassLoader

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/66501.html

相關(guān)文章

  • Java的類加載機(jī)制

    摘要:如果需要支持類的動(dòng)態(tài)加載或需要對(duì)編譯后的字節(jié)碼文件進(jìn)行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機(jī)制。任何之類的字節(jié)碼都無(wú)法調(diào)用方法,因?yàn)樵摲椒ㄖ荒茉陬惣虞d的過程中由調(diào)用。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級(jí)算法 GC策略的評(píng)價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)...

    aervon 評(píng)論0 收藏0
  • 從一無(wú)所知到無(wú)所不知————jvm系列(1)

    摘要:學(xué)習(xí)能更深入的理解這門語(yǔ)言,能理解語(yǔ)言底層的執(zhí)行過程,深入到字節(jié)碼層次。 目錄 ? 前言 程序的運(yùn)行 1.JVM類加載機(jī)制 ①一般在什么情況下會(huì)去加載一個(gè)類?也就是說(shuō),什么時(shí)候.class字節(jié)碼文件中加載這個(gè)類到JVM內(nèi)存里來(lái)? ②驗(yàn)證、準(zhǔn)備、初始化 ③初始化 2.類加載器和雙親委派機(jī)制 ...

    Betta 評(píng)論0 收藏0
  • Java類加載機(jī)制

    摘要:當(dāng)前類加載器和所有父類加載器都無(wú)法加載該類時(shí),拋出異常。加載兩份相同的對(duì)象的情況和不屬于父子類加載器關(guān)系,并且各自都加載了同一個(gè)類。類加載機(jī)制與接口當(dāng)虛擬機(jī)初始化一個(gè)類時(shí),不會(huì)初始化該類實(shí)現(xiàn)的接口。 類加載機(jī)制 概念 類加載器把class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,存放在方法區(qū),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來(lái)封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。 1、加載: 查...

    aaron 評(píng)論0 收藏0
  • jvm類加載機(jī)制

    摘要:前面提到,對(duì)于數(shù)組類來(lái)說(shuō),它并沒有對(duì)應(yīng)的字節(jié)流,而是由虛擬機(jī)直接生成的。對(duì)于其他的類來(lái)說(shuō),虛擬機(jī)則需要借助類加載器來(lái)完成查找字節(jié)流的過程。驗(yàn)證階段的目的,在于確保被加載類能夠滿足虛擬機(jī)的約束條件。 Java 虛擬機(jī)將字節(jié)流轉(zhuǎn)化為 Java 類的過程。這個(gè)過程可分為加載、鏈接以及初始化 三大步驟。 加載是指查找字節(jié)流,并且據(jù)此創(chuàng)建類的過程。加載需要借助類加載器,在 Java 虛擬機(jī)中,類...

    lastSeries 評(píng)論0 收藏0
  • 詳細(xì)深入分析 Java ClassLoader 工作機(jī)制

    摘要:作用負(fù)責(zé)將加載到中審查每個(gè)類由誰(shuí)加載父優(yōu)先的等級(jí)加載機(jī)制將字節(jié)碼重新解析成統(tǒng)一要求的對(duì)象格式類結(jié)構(gòu)分析為了更好的理解類的加載機(jī)制,我們來(lái)深入研究一下和他的方法。就算兩個(gè)是同一份字節(jié)碼,如果被兩個(gè)不同的實(shí)例所加載,也會(huì)認(rèn)為它們是兩個(gè)不同。 申明:本文首發(fā)于 詳細(xì)深入分析 ClassLoader 工作機(jī)制 ,如有轉(zhuǎn)載,注明原出處即可,謝謝配合。 什么是 ClassLoader ? 大家...

    mdluo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<