Java类加载与实例化过程

0x00 背景知识

1、虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法(下文将这三种统称为静态代码块)进行一次初始化
具体过程是:
①装(加)载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,之后可以用Class对象进行相关的反射操作。
②连接分为三个子步骤    

    验证:确保被加载的类的正确性

    准备:为类的静态变量分配内存,并将其初始化为默认值

    解析: 把类中的符号引用转换为直接引用

③初始化为为类的静态变量赋予正确的初始值

2、类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法
3、类实例销毁时候,首先销毁子类部分,再销毁父类部分


成员变量和静态变量的区别:
1,成员变量所属于对象。所以也称为实例变量。
静态变量所属于类。所以也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象创建而存在。随着对象被回收而消失。
静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。
作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。(静态成员就是静态代码块)

//静态代码块:在java中使用static关键字声明的代码块。静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。
//如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。
//注意:1 静态代码块不能存在于任何方法体内。2 静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。


class Code{
    {
      System.out.println("Code的构造块");
    }

    static{
        System.out.println("Code的静态代码块");
        }

    public Code(){
        System.out.println("Code的构造方法");
        }
    }


public class CodeBlock03{
     {
      System.out.println("CodeBlock03的构造块");    
     }

     static{
        System.out.println("CodeBlock03的静态代码块");
        }

        public CodeBlock03(){
             System.out.println("CodeBlock03的构造方法");
            }

      public static void main(String[] args){
            System.out.println("CodeBlock03的主方法");
            new Code();
            new Code();
            new CodeBlock03();
            new CodeBlock03();
          }
    }
/*
CodeBlock03的静态代码块
CodeBlock03的主方法
Code的静态代码块
Code的构造块
Code的构造方法
Code的构造块
Code的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
*/

构造代码块:类中的独立代码块,与对象相关,对象调用一次就执行一次。
作用:给所有的对象进行初始化。

//构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。

public class CodeBlock02{
    {
      System.out.println("第一代码块");    
    }

    public CodeBlock02(){
        System.out.println("构造方法");
        }

        {
          System.out.println("第二构造块");
        }
      public static void main(String[] args){
          new CodeBlock02();
          new CodeBlock02();
          new CodeBlock02();

    }
}    

/*
*
执行结果:
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
*/

0x01先来几个小测试:

测试一

class Singleton {
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

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

    public static Singleton getInstance() {
        return sin;
    }
}

public class Test {
    public static void main(String[] args) {
        Singleton sin = Singleton.getInstance();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

测试二:

class Singleton {   
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton sin = new Singleton();

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

    public static Singleton getInstance() {
        return sin;
    }
}

public class Test {
    public static void main(String[] args) {
        Singleton sin = Singleton.getInstance();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

测试三:

class Singleton {
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

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

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

测试四:

class Singleton {

    public static int counter1;
    public static int counter2 = 0;
    private static Singleton sin = new Singleton();

    {
        counter1=3;
    }

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

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

结果:
测试一:1,0
测试二:1,1
测试三:2,1
测试四:4,2

0x02类加载

测试一分析:
按类加载的流程走一遍:
①装(加)载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,之后可以用Class对象进行相关的反射操作。(都一样)
②连接分为三个子步骤 
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
测试一中:sin=NULL;counter1=0;counter2=0;
解析: 把类中的符号引用转换为直接引用
③初始化为为类的静态变量赋予正确的初始值
测试一中第一句:
private static Singleton sin = new Singleton();//执行后counter1=1;counter2=2;
第二句:public static int counter1;//执行后counter1=1;
第二句:public static int counter2 = 0;//执行counter2=0;
所以最后结果为1,0
测试二同理。

0x03类的实例化

类实例创建过程简单总结一下就是(new一个类的时候到底发生了什么?):
默认初始化(对成员赋0或NULL)—>进构造函数—>super()【父类也是该过程】—>显式初始化—>构造代码块—>构造函数中其它代码

class Singleton {

    /*
     *静态代码块(静态成员)
     */
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    /*
     *构造代码块
     */
    {
        counter1=3;
    }

    /*
     *造函数
     */
    public Singleton() {
        //类Singleton 的super class为object
        super();

        //构造函数中其它代码
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

根据上面总结的流程走一遍:

类加载阶段:跟测试一一样,该阶段结束后counter1=1;counter2=0;
类实例化阶段:
默认初始化(对成员赋0NULL//没有普通成员
—>进构造函数//开始进入Singleton()
—>super()【父类也是该过程】//进入super(),即Object加载
—>显式初始化//super()执行完后开始显示为成员赋值,本测试中没有普通的非static成员
—>构造代码块 //没有
—>构造函数中其它代码//       counter1++;counter2++;结束时counter1=2,counter2=1

测试四也走一遍:

类加载阶段:跟测试一一样,该阶段结束后counter1=1;counter2=1;
类实例化阶段:
默认初始化(对成员赋0NULL//没有普通成员
—>进构造函数//开始进入Singleton()
—>super()【父类也是该过程】//进入super(),即Object加载
—>显式初始化//super()执行完后开始显示为成员赋值,本测试中没有普通的非static成员
—>构造代码块 //counter1=3;
—>构造函数中其它代码//       counter1++;counter2++;结束时counter1=4,counter2=2

0x04 自测题

public class Farther
{
    static
    {
        System.out.println("静态代码块      farther");
    }

    {
        System.out.println("构造代码块  farther");
    }
    public Farther()
    {
        System.out.println("call farther counter1="+Singleton.counter1+"  counter2="+Singleton.counter2);

        Singleton.counter1++;
    }

}

class Singleton extends Farther {   

    static
    {
        System.out.println("静态代码块1  counter1="+Singleton.counter1 +"  counter2="+Singleton.counter2);
    }

    public static int counter1;
    public static int counter2 = 1;

    static
    {
        System.out.println("静态代码块2  counter1="+counter1 +"  counter2="+counter2);
    }

    private static Singleton sin = new Singleton();
    static
    {
        System.out.println("静态代码块3  counter1="+counter1 +"  counter2="+counter2);
    }

    {
        counter1++;
        System.out.println("构造代码块  counter1="+counter1+"  counter2="+counter2);
    }

    public Singleton() {
        System.out.println("call Singleton");
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        //Singleton sin = Singleton.getInstance();
        Singleton sin = new Singleton();
        System.out.println("counter1="+sin.counter1);
        System.out.println("counter2="+sin.counter2);

    }
}
/*
静态代码块      farther
静态代码块1  counter1=0  counter2=0
静态代码块2  counter1=0  counter2=1
构造代码块  farther
call farther counter1=0  counter2=1
构造代码块  counter1=2  counter2=1
call Singleton
静态代码块3  counter1=3  counter2=2
构造代码块  farther
call farther counter1=3  counter2=2
构造代码块  counter1=5  counter2=2
call Singleton
counter1=6
counter2=3

*/

执行过程就是:
1.先加载 Farther
打印了:“静态代码块 farther”
2.后加载Singleton
先打印了:“静态代码块1 counter1=0 counter2=0“
”静态代码块2 counter1=0 counter2=1”
静态代码块: private static Singleton sin = new Singleton();调用了Singleton的构造函数,
先执行super(),所以“call farther counter1=0 counter2=1”被打印;
再执行构造代码块,所以“构造代码块 counter1=2 counter2=1”被打印
再执行构造函数中其他的代码,所以“call Singleton”被打印,构造函数调用结束。
接着执行静态代码块,所以“静态代码块3 counter1=3 counter2=2”被打印。
3.在main函数中调用Singleton sin = new Singleton();
默认初始化
—>进构造函数
—>super()【父类也是该过程,可看做该过程的一次递归】,//所以“构造代码块 farther”和“call farther counter1=3 counter2=2”被打印
—>显式初始化
—>构造代码块 //所以“构造代码块 counter1=5 counter2=2”被打印
—>构造函数中其它代码 //所以“call Singleton”被打印
最后打印:counter1=6
counter2=3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章