首页 > 其他 > 详细

类加载机制1——类的加载过程

时间:2021-05-28 22:41:49      阅读:23      评论:0      收藏:0      [点我收藏+]

1、类加载的时机

有且仅有以下6种情况,若类还没有初始化,则必须对类进行初始化,这6种场景中的行为称为对一个类型的主动引用。

注:初始化是类加载过程中的最后一个步骤,因此类初始化完成,则说明其前面的步骤都已完成。

(1)遇到new、getstatic、putstatic和invokestatic这四条字节码指令时。能够生成这4个字节码指令的典型Java代码场景有

  • 使用new关键字实例化对象
  • 读取或设置一个类的静态字段(final字段除外,final字段已在编译期将结果放入了常量池中,本质上没有直接引用到定义常量的类,因此不会触发该类的初始化)
  • 调用一个类的静态方法

(2)反射调用时,会触发类的初始化

Class.forName("ClassLoadTest");

(3)初始化类的时候,若其父类未初始化,则会触发其父类的初始化,先加载父类

(4)虚拟机启动的时候,会从主类开始执行(包含main方法的类),虚拟机会先初始化这个主类

(5)使用MethodHandle去获取方法句柄时,若是读取或设置静态字段的方法,或这是new实例化的方法,则会触发对应类的初始化

(6)若接口中有默认方法(default方法),当实现此接口的类被初始化时,会先初始化此接口。

 

除以上6种场景之外,所有其它的引用类型的方式都不会触发类的加载,这种称为被动引用.

(1)通过子类引用父类的静态字段,不会导致子类的初始化

(2)通过数组定义来引用类,不会触发此类的初始化

(3)常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类。

 

测试用例

class Parent {
    protected static String name = "parent";

    {
        System.out.println("Parent 普通代码块");
    }

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

    Parent() {
        System.out.println("Parent 构造器");
    }
}

class Child extends Parent{
    public static final String CONSTANT = "常量字段";

    public static String CLASS_FIELD = "类变量字段";

    {
        System.out.println("Child 普通代码块");
    }

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

    Child() {
        System.out.println("Child 构造器");
    }
}

 

测试1:通过子类引用父类的静态字段,不会触发子类的初始化

System.out.println(Child.name);

输出1:

Parent 静态代码块

 

测试2:通过数组的定义来引用类,不会触发类的初始化

Child[] children = new Child[10];

输出2:

没有输出

 

测试3:调用常量,不会触发类的初始化

String s = Child.CONSTANT;

输出3:

没有输出

 

测试4:初始化子类的时候,会先初始化父类

String s = Child.CLASS_FIELD;

输出4:

Parent 静态代码块
Child 静态代码块

 

2、类加载的过程

加载——验证——准备——解析——初始化

(1)加载

通过类的全限定名获取类的二进制字节流,并将字节流转化为方法区的运行时数据结构。

在堆内存中生成一个代表该类的java.lang.Class的对象,作为方法区这个类的各种数据的访问入口。

(2)验证

校验Class文件种的字节流是否符合JVM规范

(3)准备

给类变量分配内存,并为类变量设置系统初始值(系统的零值)

在JDK8之前,类变量的内存都是在方法区分配。

在JDK8及之后,类变量随Class对象一起分配在堆内存中。

(4)解析

将常量池内的符号引用替换为直接引用。

符号引用:只是一种符号用来描述所引用的目标,符合JVM的规范,与实际内存指向无关

直接引用:可实际定位到内存中的位置

(5)初始化

执行类中所有类变量的赋值动作和static代码块。

因此,在1中,只需要通过是否执行了static代码块的内容,就可以知道类是否被初始化。

 

3、类的初始化 VS 类的实例化

(1)初始化是类加载过程中的最后一个环节,实例化时创建对象的环节。

(2)一个类只会加载一次,加载完会在堆内存中生成一个代表该类的Class对象

(3)类的初始化是执行类构造器的clinit方法,这个方法是由编译器自动生成的,它按代码的实际顺序,收集了类中所有类变量的赋值动作和static代码块。

  实例化是执行类的普通代码块和实例对象的赋值。

(4)new Child() 过程的执行顺序是

  • 加载父类(执行父类的static赋值和static代码块)
  • 加载子类(执行子类的static赋值和static代码块)
  • 执行父类的普通代码块
  • 执行父类的构造器方法
  • 执行子类的普通代码块
  • 执行子类的构造器方法

测试:

new Child();

输出:

Parent 静态代码块
Child 静态代码块
Parent 普通代码块
Parent 构造器
Child 普通代码块
Child 构造器

 


参考书籍:

1.《深入理解Java虚拟机》第3版,作者:周志明

 

类加载机制1——类的加载过程

原文:https://www.cnblogs.com/a-candy/p/14823850.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!