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方法

posted @ 2018-03-16 16:29  架构之美,智慧之光  阅读(3182)  评论(0编辑  收藏  举报