JVM簡介(二)——類加載及代碼執行順序


建議參看 JVM簡介(一)——內存模型,對照着圖看本文
 
        一。類加載流程
        加載——>連接——>類初始化——>類實例化——>使用——>卸載
 
        加載——將.class文件載入到方法區。這樣常量和類相關信息還有方法已經在方法區
        連接——驗證:驗證合法性,保證能讓JVM正常執行
                       准備:為靜態變量在方法區開辟內存,並初始化。基本類型設為0,非基本類型設為NULL,
                                  常量設為程序中的賦值(只有常量設為程序中值,非常量的靜態變量都是賦JVM默認值,
                                  而非程序設定的值)
                       解析:把常量池中的常量從符號引用變成直接引用。即給出絕對地址
       (一個類只有被直接引用,才會觸發類初始化,直接引用場景見本文第二節)
        類初始化——執行static的賦值語句。包括static變量的賦值,以及static代碼塊。自上而下執行。
       (只有需要實例化的時候,才會觸發類實例化)

        實例化——執行成員變量的賦值語句和構造函數。包括成員變量的賦值,以及{}代碼塊。自上而下執行。

                          構造函數最后執行 

        使用——每個線程一個,調用方法,就往里面壓一個Stack Frame。方法執行結束后則彈出。實例化對

                       象后,對象是在中,中有個引用指向中的對象,中的對象有個地址指向方法區中的

                       類的信息

        卸載——略

 

        二。直接引用

         以下是視為主動使用一個類,其他情況均視為被動使用!

        1)   最為常用的new一個類的實例對象(聲明不叫主動使用)

        2)   直接調用類的靜態方法。

        3)   對類的靜態變量進行讀取、賦值操作的

        4)   反射調用一個類的方法。

        5)   初始化一個類的子類的時候,父類也相當於被程序主動調用了(如果調用子類的靜態變量是從父類

              繼承過來並沒有復寫的,那么也就相當於只用到了父類的東東,和子類無關,所以這個時候子類

              不需要進行類初始化)。

        6)   直接運行一個main函數入口的類。

 

       三。類初始化與對象實例化

        (1)類初始化流程:

                        <1>父類靜態變量賦值、靜態代碼塊執行(形式如static {})

                        <2>子類靜態變量賦值、靜態代碼塊執行形式如static {})

 

                 對象實例化流程:

                        <1>父類成員變量賦值、成員代碼塊執行(形式如{})

                        <2>父類構造函數

                        <3>子類成員變量賦值、成員代碼塊執行(形式如{})

                        <4>子類構造函數

 

        (2)類初始化只需執行一遍,對象實例化可執行多遍

        (3)實例化時需類初始化(沒初始化過),但是並不一定是初始化做完再實例化。初始化進行到一半,

                 遇到實例化,會先執行實例化的部分,再繼續執行初始化的部分。初始化可以被實例化即時打斷,

                 但是實例化時建議就不要再實例化自己以及父類相關對象,容易死循環

        (4)main方法會在所在類初始化后執行

 

        四。簡單例題

        梳理至此,應該是把整個加載過程理順了。我自己網上找了些類似的搞來搞去關於加載執行順利的題,

        都能准確應付了。下面來試試手,也歡迎各位找出各種變態奇葩各種中二的題目或想法來討論。

        Class A extends B{

          A(){

            System.out.println("A-new");

          } 

          {

            System.out.println("A-1");

          } 

          static{

            System.out.println("A-static-1");

            new A();

          } 

          public static void main(String[] args){

            System.out.println("A-main");

          }

          static{

            System.out.println("A-static-2");

          }

        }

 

        Class B{

          B(){

            System.out.println("B-new");

          }

          static{

            System.out.println("B-static-1");

          }

          {

            System.out.println("B-1");

          }

        }

 

        執行結果是:

        B-static-1——main函數在A中,A繼承自B,故先走父類B的初始化流程,B靜態代碼塊執行

        A-static-1——走完父類B的初始化流程后,再走子類A的初始化流程,A靜態代碼塊執行

        B-1——其實子類A的初始化流程還沒走完,但是遇到了new A(),就直接開始走A的實例化流程,

                     先走父類B實例化流程,B的成員代碼塊執行

        B-new——繼續B的實例化流程,走B的構造函數

        A-1——父類B的實例化流程結束,走子類A的實例化流程,A的成員代碼塊執行

        A-new——繼續子類A的實例化流程,走A的構造函數

        A-static-2——A實例化結束后,繼續之前被打斷的類A的初始化流程

        A-main——main所在類A的初始化結束后,走main方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM