标签:
通过之前的介绍可知,类加载过程共有5个步骤,分别是:加载、验证、准备、解析、初始化。其中,验证、准备、解析称为连接。下面详细介绍这5个过程JVM所做的工作。
注意:“加载”是“类加载”过程的第一步,千万不要混淆。
在加载过程中,JVM主要做3件事情:
JVM规范对于加载过程给予了较大的宽松度。一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取:
数组也有类型,称为“数组类型”。如:
String[] str = new String[10];
这个数组的数组类型是Ljava.lang.String,而String只是这个数组中元素的类型。
当程序在运行过程中遇到new关键字创建一个数组时,由JVM直接创建数组类,再由类加载器创建数组中的元素类。
而普通类的加载由类加载器完成。既可以使用系统提供的引导类加载器,也可以使用用户自定义的类加载器。
验证阶段比较耗时,它非常重要但不一定必要,如果所运行的代码已经被反复使用和验证过,那么可以使用-Xverify:none参数关闭,以缩短类加载时间。
验证是为了保证二进制字节流中的信息符合虚拟机规范,并没有安全问题。
虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行,如果出现这些情况,编译无法通过。也就是说,Java语言的安全性是通过编译器来保证的。
但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。通过上文可知,虚拟机规范中没有限制二进制字节流的来源,那么任意来源的二进制字节流虚拟机都能接受,为了防止字节流中有安全问题,因此需要验证!
文件格式验证
这个阶段主要验证输入的二进制字节流是否符合class文件结构的规范。二进制字节流只有通过了本阶段的验证,才会被允许存入到方法区中。
本验证阶段是基于二进制字节流的,而后面的三个验证阶段都是在方法区中进行,并基于类特定的数据结构的。
通过上文可知,加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区。而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区。也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作。这个过程印证了:加载和验证是交叉进行的。
元数据验证
本阶段对方法区中的字节码描述信息进行语义分析,确保其符合Java语法规范。
字节码验证
本阶段是验证过程的最复杂的一个阶段。
本阶段对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。
符号引用验证
本阶段验证发生在解析阶段,确保解析能正常执行。
准备阶段完成两件事情:
1. 为已经在方法区中的类中的静态成员变量分配内存
类的静态成员变量也存储在方法区中。
2. 为静态成员变量设置初始值
初始值为0、false、null等。
示例1:
public static String name = "柴毛毛";
在准备阶段,JVM会在方法区中为name分配内存空间,并赋上初始值null。
给name赋上”柴毛毛”是在初始化阶段完成的。
示例2:
public static final String name = "柴毛毛";
被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性中,在准备阶段就将constantValue的值赋给该字段。
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
初始化阶段就是执行类构造器clinit()的过程。
clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。
标签:
原文地址:http://blog.csdn.net/u010425776/article/details/51254858