JVMAndroid开发程序员

类加载的过程

2017-12-19  本文已影响61人  紫霞等了至尊宝五百年

1 加载

注意:“加载”是“类加载”(Class Loading)过程的第一步

1.1 加载的过程

在加载过程中,JVM主要做3件事情

程序在运行中所有对该类的访问都通过这个类对象,也就是这个Class对象是提供给外界访问该类的接口

1.2 从哪里加载

JVM规范对于加载过程给予了较大的宽松度.一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取

1.3 类和数组加载过程的区别

数组也有类型,称为“数组类型”.如:
String[] str = new String[10];
这个数组的数组类型是Ljava.lang.String,而String只是这个数组的元素类型.

当程序在运行过程中遇到new关键字创建一个数组时,
由JVM直接创建数组类,再由类加载器创建数组中的元素类型.

而普通类的加载由类加载器创建.既可以使用系统提供的引导类加载器,也可以由用户自定义的类加载器完成(即重写一个类加载器的loadClass()方法).

1.4 加载过程的注意点

2 验证

验证阶段比较耗时,它非常重要但不一定必要(因为对程序运行期没有影响),如果所运行的代码已经被反复使用和验证过,那么可以使用-Xverify:none参数关闭,以缩短类加载时间

2.1 验证的目的

验证是为了保证二进制字节流中的信息符合虚拟机规范,并没有安全问题.

2.2 为什么需要验证

虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行.也就是说,Java语言的安全性是通过编译器来保证的.

但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。

通过上文可知,虚拟机规范中没有限制二进制字节流的来源,在字节码层面上,上述Java代码无法做到的都是可以实现的,至少语义上是可以表达出来的,为了防止字节流中有安全问题,需要验证!

2.3 验证的过程

通过上文可知,加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区。
而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区。
也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作。这个过程印证了:加载和验证是交叉进行的。

3 准备

准备阶段完成两件事情:

public static final int value = 123;

准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性中,在准备阶段就将constantValue的值赋给该字段(此处将value赋为123).

4 解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程.

5 初始化

真正开始执行类中定义的Java程序代码(或者说是字节码)
初始化阶段就是执行类构造器clinit()的过程.

clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。

初始化过程的注意点

public class Test {
    static {
        i=0;
        System.out.println(i);//编译失败:"非法向前引用"
    }
    static int i = 1;
}

其他线程虽会被阻塞,只要有一个clinit()方法执行完,其它线程唤醒后不会再进入clinit()方法.同一个类加载器下,一个类型只会初始化一次.

上一篇下一篇

猜你喜欢

热点阅读