1. 從 static 關(guān)鍵字深入理解 java對象初始化順序

        共 3770字,需瀏覽 8分鐘

         ·

        2020-11-24 14:07

        點(diǎn)擊上方藍(lán)色?猿芯”?關(guān)注,輸入1024,你懂的

        前言

        最近在閱讀 ThreadLocal 源碼的時候,發(fā)現(xiàn)一段很有意思的代碼,代碼片段如下:

        private?final?int?threadLocalHashCode?=?nextHashCode();?
        private?static?AtomicInteger?nextHashCode?=?new?AtomicInteger();
        private?static?final?int?HASH_INCREMENT?=?0x61c88647;

        private?static?int?nextHashCode()?{
        ????return?nextHashCode.getAndAdd(HASH_INCREMENT);
        }

        以上代碼片段主要是 ThradLocal 生成哈希值(threadLocalHashCode)的邏輯,通過靜態(tài)的原子整型變量 nextHashCode 以及靜態(tài)方法 nextHashCode (),為每個線程持有的 ThreadLocal 本地變量生成唯一 的 hashCode

        注:ThreadLocalhashCode 選擇 HASH_INCREMENT 變量值:0x61c88647 很有意思,里面涉及到斐波那契數(shù)列黃金分割法,感興趣的同學(xué)可以自行了解下。

        當(dāng)然本文的重點(diǎn)不是 ThreadLocal 原理分析上,而是分析 static 關(guān)鍵字修飾的靜態(tài)域(靜態(tài)變量、靜態(tài)塊)順序加載問題。

        這段代碼總共四行,除了第一行都是用 static 關(guān)鍵字修飾的,這里我們設(shè)想一個問題,當(dāng)類初始化的時候,這四行代碼是從上往下執(zhí)行的嗎?

        答案是:”否“。

        靜態(tài)變量

        為了方便 debug 調(diào)試,我們把上面的代碼稍微做了下調(diào)整,代碼片段如下:

        public?class?Static01?{
        ????private?final?int?threadLocalHashCode?=?nextHashCode();

        ????private?static?AtomicInteger?nextHashCode?=?new?AtomicInteger();

        ????private?static?final?int?HASH_INCREMENT?=?getIncr();

        ????public?Static01(){
        ????????System.out.println("threadLocalHashCode::"?+?threadLocalHashCode);
        ????}

        ????private?static?int?getIncr()?{
        ????????return?0x61c88647;
        ????}

        ????private?static?int?nextHashCode()?{
        ????????return?nextHashCode.getAndAdd(HASH_INCREMENT);
        ????}

        ????public?static?void?main(String[]?args)?{
        ????????new?Static01();
        ????}

        }

        上面的代碼片段用 debug 模式啟動,通過為每行代碼打斷點(diǎn),發(fā)現(xiàn)當(dāng)真正實例化 Static01 類時,代碼運(yùn)行順序并非是按照逐行執(zhí)行,而是如下圖紅色標(biāo)記順序進(jìn)行的。

        其執(zhí)行流程是:

        • 第一步、用 new 關(guān)鍵字初始化 Static01 類的構(gòu)造方法
        • 第二步、初始化靜態(tài)變量 nextHashCode
        • 第三步、初始化靜態(tài)變量 HASH_INCREMENT
        • 第四步、初始化成員變量 threadLocalHashCode
        • 最后 、在 Static01 構(gòu)造方法打印 threadLocalHashCode 變量的 hash

        對象實例化

        就是執(zhí)行類中構(gòu)造函數(shù)的內(nèi)容,如果該類存在父類,會通過顯示或者隱示的方式(super方法)先執(zhí)行父類的構(gòu)造函數(shù),在堆內(nèi)存中為父類的實例變量開辟空間,并賦予默認(rèn)的初始值,然后在根據(jù)構(gòu)造函數(shù)的代碼內(nèi)容將真正的值賦予實例變量本身,然后,引用變量獲取對象的首地址,通過操作對象來調(diào)用實例變量和方法

        從上面代碼執(zhí)行流程可以看出

        • 在對象實例化之前必須先初始化 static 修飾的靜態(tài)變量,并且靜態(tài)變量也是有加載順序的;
        • 類的成員變量的初始化在構(gòu)造方法里面進(jìn)行,加載順序優(yōu)先于構(gòu)造方法體的執(zhí)行語句。
        • 如果某類繼承了父類,那么必須先初始化父類的構(gòu)造方法以及成員變量以及構(gòu)造方法的執(zhí)行語句,然后才是子類的成員變量以及構(gòu)造方法的執(zhí)行語句。
        public?Static01()?{
        ????super();
        ????System.out.println("threadLocalHashCode::"?+?threadLocalHashCode);
        }

        另外,靜態(tài)語句塊中只能訪問到定義在靜態(tài)塊之前的變量,在靜態(tài)塊里可以給該變量賦值,但是不能訪問,否則編譯器會提示 “Illegal forward reference” 錯誤,如下圖

        靜態(tài)塊

        靜態(tài)塊主要用于類的初始化,不是指對象的實例化。它只會執(zhí)行一次,靜態(tài)塊只能訪問類的靜態(tài)成員屬性和方法,不能在靜態(tài)塊使用 this。

        我們先把上面的代碼稍加改造下,增加 “靜態(tài)塊1”和“靜態(tài)塊2” 靜態(tài)塊代碼

        private?final?int?threadLocalHashCode?=?nextHashCode();
        private?static?AtomicInteger?nextHashCode?=?new?AtomicInteger();

        static{
        ????System.out.println("靜態(tài)塊1");
        }

        private?static?final?int?HASH_INCREMENT?=?getIncr();

        static{
        ????System.out.println("靜態(tài)塊2");
        }

        運(yùn)行結(jié)果如下:

        發(fā)現(xiàn)不管是靜態(tài)塊還是靜態(tài)變量,它們之間都是按順序執(zhí)行的。那為什么是靜態(tài)塊、靜態(tài)變量的初始化是有順序的呢?

        通過查看 Static01 類的 class 編譯文件,發(fā)現(xiàn)編譯器會把 static 塊的代碼放在同一 static 花括號{}內(nèi)。

        代碼順序是按照之前編碼的順序整合,這么看來是編譯器在作怪吧。

        static?{
        ????System.out.println("靜態(tài)塊1");
        ????HASH_INCREMENT?=?getIncr();
        ????System.out.println("靜態(tài)塊2");
        }

        類加載中,靜態(tài)域的加載時機(jī)

        從《深入理解Java虛擬機(jī):JVM高級特性與最佳實踐(第2版)》這本書講的類加載機(jī)制原理可知:

        當(dāng)遇到newgetstaticputstaticinvokestatic4條字節(jié)碼指令時,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

        這就解釋了為什么在對象未實例化前,可以通過 “類名.靜態(tài)屬性變量、類名.靜態(tài)方法” 的方式訪問靜態(tài)變量和靜態(tài)方法了。

        類加載的時機(jī)

        對于初始化階段,虛擬機(jī)規(guī)范規(guī)定了有且只有 5 種情況必須立即對類進(jìn)行“初始化”(而加載、驗證、準(zhǔn)備自然需要在此之前開始):

        1. 遇到new、getstatic 和 putstatic 或 invokestatic 這4條字節(jié)碼指令時,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。對應(yīng)場景是:使用 new 實例化對象、讀取或設(shè)置一個類的靜態(tài)字段(被 final 修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、以及調(diào)用一個類的靜態(tài)方法。
        2. 對類進(jìn)行反射調(diào)用的時候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
        3. 當(dāng)初始化類的父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。(而一個接口在初始化時,并不要求其父接口全部都完成了初始化)
        4. 虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含 main() 方法的那個類),虛擬機(jī)會先初始化這個主類。
        5. 當(dāng)使用 JDK 1.7 的動態(tài)語言支持時,如果一個 java.lang.invoke.MethodHandle 實例最后的解析結(jié)果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

        總結(jié)

        1、靜態(tài)域(靜態(tài)變量、靜態(tài)塊)是按逐行順序加載的,并且靜態(tài)域只會加載一次。

        2、當(dāng)實例化對象之前(構(gòu)造方法調(diào)用),會先去初始化靜態(tài)域,再去調(diào)用構(gòu)造函數(shù)實例化對象。

        3、一般對象初始化順序如下:父類的靜態(tài)域順序加載–>子類靜態(tài)域順序加載–>父類非靜態(tài)域初始化->父類構(gòu)造函數(shù)初始化–>子類非靜態(tài)域初始化->子類構(gòu)造函數(shù)初始化。

        參考

        • https://blog.csdn.net/qq_36522306/article/details/80584595
        • https://www.cnblogs.com/cxiang/p/10082160.html
        瀏覽 57
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
          
          

            1. 精品色999 | av啪啪漫画羞羞汗汗sss | 韩国一级做A片免费播放 | 裸体鞭打屁股xxxx | zzijzzij日本丰满少妇 |