jvm--类的加载,连接,初始化

时间:2021-07-13 17:28:15

java是一门静态语言(先编译,再执行),但是它同时具有很多动态语言的特性。java 中类的加载,连接,初始化是程序在运行期间完成的。

  • 加载 : 将编译后的class文件加载到内存。同时在方法区会生成Class对象,这个class对象反映了类的数据结构。

文件加载的方式有

  1. 从本地系统直接加载class文件
  2. 通过网络远程加载class文件
  3. 从zip,jar中加载class文件
  4. 程序运行期间动态将源文件加载成class文件,参照java设计模式中的动态代理
  • 连接 : 连接分为三个阶段
  1. 验证:验证class文件是否符合jvm规范,因为class文件可以被人为篡改。
  2. 准备:类的静态变量分配内存空间,并赋默认值。(比如int的默认值是0,引用对象的默认值是null)
  3. 解析 :  符号引用转换成直接引用。
  • 初始化: 初始化阶段实际是执行<clinit>()的过程,包括 类的静态变量赋值;执行类的静态代码块 static{ }。执行的顺序按照源文件中代码的顺序(从上往下)

类的初始化场景:类在主动引用的时候会执行初始化,以下场景是类的主动引用

  1. 创建类的实例,即创建对象
  2. 访问或者修改类的静态变量
  3. 调用类的静态方法
  4. 反射
  5. 初始化一个类的子类(会先初始化父类)
  6. 启动类,即main方法所在的类
/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class ClassInitTest {
    public static void main(String[] args) {
        System.out.println(Child.str1);
    }

    static{
        System.out.println("main static block.......");
    }

}

class Parent{
    static String str1 = "hello";
    static{
        System.out.println("parent static block.....");

    }

}
class Child extends Parent{
    static  String str2 = "world";
    static{
        System.out.println("child static block.......");
    }
}

 

分析下:

  1. 访问Child.str1会执行类的初始化,会执行类的静态代码块内容
  2. 对于静态变量,只有直接定义了该变量的类才会被初始化。因此Child类不会执行初始化
  3. 输出结果:

main static block.......
parent static block.....
hello

 

      

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class ClassInitTest {
    public static void main(String[] args) {
        System.out.println(Child.str2);
    }

    static{
        System.out.println("main static block.......");
    }

}

class Parent{
    static String str1 = "hello";
    static{
        System.out.println("parent static block.....");

    }

}
class Child extends Parent{
    static  String str2 = "world";
    static{
        System.out.println("child static block.......");
    }
}

 

分析下:

  1. 访问Child.str2会执行Child类的初始化,由于它继承Parent,因此会先执行Parent初始化
  2. 输出结果:

main static block.......
parent static block.....
child static block.......
world

 

常量(编译期):都知道java中常量是存放在常量池中的,有两个问题

1.访问常量是否会触发类的初始化呢?

2.它是放在哪个类对应的常量池中的呢?

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class ClassInitTest {
    public static void main(String[] args) {
        System.out.println(Child.MSG);
    }

}

class Parent{
    static String str1 = "hello";
    static final String MSG = "success";


    static{
        System.out.println("parent static block.....");

    }

}
class Child extends Parent{
    static  String str2 = "world";
    static{
        System.out.println("child static block.......");
    }
}

输出:success   

说明不会触发该常量所在类的初始化

 

本地手动删除Child.class,Parent.class文件,运行上述程序

输出:success   

总结:常量是编译期间存放在调用常量的方法所在类的常量池中,本质上并没有引用到定义常量的所在类。因此不会触发定义常量所在类的初始化。

 

常量(运行期):和编译期常量不同,运行期常量是在运行期间确定的,它不会放在调用该常量类的常量池中,因此会触发定义该常量类的初始化

import java.util.UUID;

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class ClassInitTest {
    public static void main(String[] args) {
        System.out.println(Child.MSG);

    }

}

class Parent{
    static String str1 = "hello";

    static{
        System.out.println("parent static block.....");

    }

}
class Child extends Parent{
    static final String MSG = new String("msg");
    static{
        System.out.println("child static block.......");
    }
}

输出:

parent static block.....
child static block.......
msg

 

数组:数组的本质是一个特殊的class,它是由jvm在运行期间动态生成的。因此它不会触发数组具体类型的初始化。查看class,都是以[  + class 表示;

import java.util.UUID;

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class ClassInitTest {
    public static void main(String[] args) {
        Child[] c = new Child[1];
        int[]a = new int[1];
        Integer[]A = new Integer[1];
        System.out.println(c.getClass());
        System.out.println(a.getClass());
        System.out.println(A.getClass());


    }

}

}

 

输出:

class [LChild;
class [I
class [Ljava.lang.Integer;

 

 

接口:接口中的变量默认都是常量,public static final 默认可以不写。接口的主动引用是否会触发初始化取决于接口中定义的常量是编译期常量还是运行期常量。

编译期常量:不会触发初始化

运行期常量:  会触发初始化

 接口与类不同的是,执行接口的<clinit>()方法,不需要执行父接口的<clinit>()方法;只有父接口中定义的变量使用时,才会执行父接口的<clinit>()方法。

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class MyTest1 {
    public static void main(String[] args) {
        System.out.println(IChild.STR2);
    }
}


interface  IParent{
    public static final String STR1 = "hello";
}

interface  IChild extends IParent{
    public static final String STR2 = "world";
}

 

输出:

world

本地手动删除IChild.class,IParent.class文件,程序可以正常输出

/**
 * Created by IntelliJ IDEA.
 * User: chenzhubing
 * Date: 2019/6/16
 */
public class MyTest1 {
    public static void main(String[] args) {
        System.out.println(IChild.STR2);
    }
}


interface  IParent{
    public static final String STR1 = "hello";
}

interface  IChild extends IParent{
    public static final String STR2 = new String("world");
}

输出:

world

删除对应class文件,程序运行会报错