java类加载过程

干Android开发两年了,竟然不清楚Java类加载过程,都有点不好意思说自己会Java,这不赶快来恶补一下这方面的知识,搞明白类加载过程后,会对其他技术知识譬如:static关键字的作用?反射机制?等等都有帮助,这也是为什么知识一定要系统化的原因。

首先我们要了解一下ClassLoader,ClassLoader是Java用于加载类的一个机制。等到程序运行时,JVM先初始化,在JVM初始化的过程中,JVM生成几个ClassLoader,JVM调用指定的ClassLoader去加载.class文件等各类路径、文件的类。
程序运行时类的加载实际过程

1、JDK执行指令去寻找jre目录,寻找jvm.dll,并初始化JVM;产生一个Bootstrap Loader(启动类加载器);
2、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
3、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。
4、最后由AppClass Loader加载Java类。

Java类加载过程即类装载器把一个类装入Java虚拟机中,总体来说包含以下过程:

编译 -> 加载 -> 链接(验证+准备+解析)->初始化(使用前的准备)->使用-> 卸载

1、加载:以二进制形式生成Class对象

⑴、通过一个类的全限定名来获取定义此类的二进制字节流。
⑵、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
⑶、在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

第一步获取二进制字节流有很多形式,因为它并没有限定二进制流从哪里来,那么我们可以 用系统的类加载器,也可以用自己的方式写加载器来控制字节流的获取 :

①从class文件来->一般的文件加载
②从zip包中来->加载jar中的类
③从网络中来->Applet

获取二进制流获取完成后会按照jvm所需的方式保存在方法区中,同时会在java堆中实例化一个java.lang.Class对象与堆中的数据关联起来。

2、链接:又分验证、准备、解析

⑴、验证:检查导入类或接口二进制数据的正确
⑵ 、准备:为静态变量初始化并分配内存地址
⑶、 解析:将符号引用转为直接引用

第一步:验证

验证又可以细分一下几个步骤: 文件格式验证->元数据验证->字节码验证->符号引用验证

文件格式验证:验证字节流是否符合Class文件格式的规范并 验证其版本是否能被当前的jvm版本所处理。ok没问题后,字节流就可以进入内存的方法区进行保存了。后面的3个校验都是在方法区进行的。
元数据验证 :对字节码描述的信息进行语义化分析,保证其描述的内容符合java语言的语法规范。
字节码检验
:最复杂,对方法体的内容进行检验,保证其在运行时不会作出什么出格的事来。
符号引用验证 :来验证一些引用的真实性与可行性,比如代码里面引了其他类,这里就要去检测一下那些来究竟是否存在;或者说代码中访问了其他类的一些属性,这里就对那些属性的可以访问行进行了检验。(这一步将为后面的解析工作打下基础)

验证的目的:确保class文件的字节流信息符合jvm的规范。假如jvm不对这些数据进行校验的话,可能一些有害的字节流会让jvm完全崩溃。
验证阶段很重要,但也不是必要的,假如说一些代码被反复使用并验证过可靠性了,实施阶段就可以尝试用-Xverify:none参数来关闭大部分的类验证措施,以简短类加载时间。

第二步:准备

这阶段会为类变量(指那些静态变量)分配内存并设置类比那辆初始值的阶段,这些内存在方法区中进行分配。这里要说明一下,这一步只会给那些静态变量设置一个初始的值,而那些实例变量是在实例化对象时进行分配的。
例如:

public static int value=123; 此时value的值为0,不是123。
private int i = 123; 此时,i 还未进行初始化,因为这句代码还不能执行。

第三步:解析

是对类的属性,方法等东西进行转换,具体涉及到Class文件的格式内容。

3、初始化:激活类的静态变量和静态代码块,初始化Java代码

要对类进行 初始化 ,代码上可以理解为 ‘为要初始化的类中的所有静态成员都赋予初始值、对类中所有静态块都执行一次,并且是按代码编写顺序执行’ 。
如下代码:输出的是‘1’。如果①和②顺序调换,则输出的是‘123’。

public class Main {
 public static void main(String[] args){
 System.out.println(Super.i);
 }
}
class Super{
 //①
 static{
 i = 123;
 }
 //②
 protected static int i = 1;
}    

主动对类进行引用”指的就是以下五种JVM规定的判定初始化与否的预处理条件。那么,其他的方式,都可归为‘类被动引用’的方式,这些方式是不会引起JVM去初始化相关类的:

1.用new实例化一个类时

读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)

2.执行静态方法的时候。

使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。

3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
4.当jvm启动时

用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。

5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。

注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化

因此类从编译、被使用,到卸载的全过程:

编译 -> 加载 -> 链接(验证+准备+解析)->初始化(使用前的准备)->使用-> 卸载

你可能感兴趣的:(java类加载过程)