前言一个 Java 文件从编码实现到最终执行,个别次要包含两个过程
编译
运行
编译,即把咱们写好的 java 文件,通过 javac 命令编译成字节码,也就是咱们常说的.class 文件。
运行,则是把编译宣称的.class 文件交给 Java 虚拟机 (JVM) 执行。
而咱们所说的类加载过程即是指 JVM 虚拟机把.class 文件中类信息加载进内存,并进行解析生成对应的 class 对象的过程。
举个艰深点的例子来说,JVM 在执行某段代码时,遇到了 class A,然而此时内存中并没有 class A 的相干信息,于是 JVM 就会到相应的 class 文件中去寻找 class A 的类信息,并加载进内存中,这就是咱们所说的类加载过程。
由此可见,JVM 不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个须要运行的类时才会加载,且只加载一次。
类加载
类加载的过程次要分为三个局部:
加载
链接
初始化
而链接又能够细分为三个小局部:
验证
筹备
解析
加载
简略来说,加载指的是把 class 字节码文件从各个起源通过类加载器装载入内存中。
这里有两个重点:
字节码起源。个别的加载起源包含从本地门路下编译生成的.class 文件,从 jar 包中的.class 文件,从近程网络,以及动静代理实时编译
类加载器。个别包含启动类加载器,扩大类加载器,利用类加载器,以及用户的自定义类加载器。
注:为什么会有自定义类加载器?
一方面是因为 java 代码很容易被反编译,如果须要对本人的代码加密的话,能够对编译后的代码进行加密,而后再通过实现本人的自定义类加载器进行解密,最初再加载。
另一方面也有可能从非标准的起源加载代码,比方从网络起源,那就须要本人实现一个类加载器,从指定源进行加载。
验证
次要是为了保障加载进来的字节流合乎虚拟机标准,不会造成平安谬误。
包含对于文件格式的验证,比方常量中是否有不被反对的常量?文件中是否有不标准的或者附加的其余信息?
对于元数据的验证,比方该类是否继承了被 final 润饰的类?类中的字段,办法是否与父类抵触?是否呈现了不合理的重载?
对于字节码的验证,保障程序语义的合理性,比方要保障类型转换的合理性。
对于符号援用的验证,比方校验符号援用中通过全限定名是否可能找到对应的类?校验符号援用中的拜访性(private,public 等)是否可被以后类拜访?
筹备
次要是为类变量(留神,不是实例变量)分配内存,并且赋予初值。
特地须要留神,初值,不是代码中具体写的初始化的值,而是 Java 虚拟机依据不同变量类型的默认初始值。
比方 8 种根本类型的初值,默认为 0;援用类型的初值则为 null;常量的初值即为代码中设置的值,final static tmp = 456,那么该阶段 tmp 的初值就是 456
解析
将常量池内的符号援用替换为间接援用的过程。
两个重点:
符号援用。即一个字符串,然而这个字符串给出了一些可能唯一性辨认一个办法,一个变量,一个类的相干信息。
间接援用。能够了解为一个内存地址,或者一个偏移量。比方类办法,类变量的间接援用是指向办法区的指针;而实例办法,实例变量的间接援用则是从实例的头指针开始算起到这个实例变量地位的偏移量
举个例子来说,当初调用办法 hello(),这个办法的地址是 1234567,那么 hello 就是符号援用,1234567 就是间接援用。
在解析阶段,虚构机会把所有的类名,办法名,字段名这些符号援用替换为具体的内存地址或偏移量,也就是间接援用。
初始化
这个阶段次要是对类变量初始化,是执行类结构器的过程。
换句话说,只对 static 润饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时蕴含多个动态变量和动态代码块,则依照自上而下的程序顺次执行。