JVM 类加载之准备阶段和初始化阶段

1,007

前言

前面已介绍过类加载的5个步骤,复习一下

  • 类的加载、连接、初始化、使用和卸载。

  • 其中连接里面还有3个子步骤:

    • 验证: 确保被加载的类的正确性。
    • 准备: 为类的静态变量分配内存,并将其初始化为默认值。(int 0,boolean false)。
    • 解析:把类中的符号引用转换为直接引用。
  • 初始化就是:为类的静态变量赋予正确的初始值

    • 对类的主动使用才会进行初始化。

更进一步

先从一段普通的代码说起

  • 代码
public class PrepareAndInit {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1: "+Singleton.counter1);
        System.out.println("counter2: "+Singleton.counter2);
    }
}
class Singleton{
    protected static int counter1;
    protected static int counter2 = 0;
    private static Singleton singleton = new Singleton();
    private Singleton(){//私有构造外部不可直接new
        counter1++;
        counter2++;
    }
    public static Singleton getInstance(){
        return singleton;
    }
}
  • 结果

image.png

将代码改编一下,将静态变量counter2移到构造方法下面怎么样

  • 代码
package com.jvmstudy.classloading;

public class PrepareAndInit {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1: "+Singleton.counter1);
        System.out.println("counter2: "+Singleton.counter2);
    }
}
class Singleton{
    protected static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton(){//私有构造外部不可直接new
        counter1++;
        counter2++;
    }
    protected static int counter2 = 0;
    public static Singleton getInstance(){
        return singleton;
    }
}
  • 结果

image.png

产生这个结果的原因

Singleton singleton = Singleton.getInstance();
  • 调用一个类的静态方法会导致这个类的初始化。

但是在初始化阶段前,会先经过连接阶段中的准备动作

准备阶段

  • 为静态变量分配内存,并赋默认值(int 0,boolean false,引用 null)
  • 准备阶段
class Singleton{
    protected static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton(){//私有构造,类的外部不可直接new
        counter1++;
        counter2++;
    }
    protected static int counter2 = 0;
    public static Singleton getInstance(){
        return singleton;
    }
}
  • counter1 = 0;
  • singleton = null;
  • 构造方法不会执行;
  • counter2 = 0;

初始化阶段

  • 为静态变量赋予正确的值,就是程序员手动赋的值。
class Singleton{
    protected static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton(){//私有构造,类的外部不可直接new
        counter1++;
        counter2++;
    }
    protected static int counter2 = 0;
    public static Singleton getInstance(){
        return singleton;
    }
}
  • counter1 = 0;(依旧是0)
  • singleton = singleton实例对象的引用;
  • 构造方法会被执行,因为上面用了new Singleton();
    • 在构造方法里面
    • counter1 = 1;
    • counter2 = 1;
  • 出了构造方法,counter2被赋予0(程序员写的);即counter2从1变成看了0;
  • 最终结果就是:
    • counter1 = 1;
    • counter2 = 0;
  • 结论
    • 初始化是从上到下执行的,初始化之前会有准备阶段(给静态变量赋予默认值)。

再次强调类的加载

都是些概念。

  • 类型的加载:就是把二进制形式的java类型读入java虚拟机中。
  • 连接:有3个子步骤
    • 验证:检查字节码(.class)文件。
    • 准备:为类变量分配内存,设置默认值。但是在到达初始化。之前,类变量都没有初始化为真正的初始值。
    • 解析:解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程
  • 初始化:为类变量赋予正确的初始值。
  • 类实例化:为新的对象分配内存为实例变量赋默认值为实例变量赋正确的初始值java编译器为它编译的每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为<init>。针对源代码中每一个类的构造方法,java编译器都产生一个<init>方法。

类加载的最终产品

  • 类的加载的最终产品是位于内存中的Class对象。
  • Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

初始化的时机

  • 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口
    • 在初始化一个类时,并不会先初始化它所实现的接口
    • 在初始化一个接口时,并不会先初始化它的父接口
    • 因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化