JAVA类加载过程/类装载的执行过程/java类加载机制/JVM加载Class文件的原理机制?
类加载的过程主要分为三个部分:(加链初,验准解)
- 加载
- 链接
- 初始化
而链接又可以细分为三个小部分:
- 验证
- 准备
- 解析
骚戴理解:首先把class字节码文件从各个来源通过类加载器装载入内存中(加载),然后验证加载进来的字节码是否符合虚拟机规范(验证),Java虚拟机为类变量分配内存,并且赋予默认初始值(准备),虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址,也就是直接引用(解析),执行类构造器对类变量进行自定义的初始化(初始化)
1、加载
简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
这里有两个重点:
字节码来源
- 一般的加载来源包括从本地路径下编译生成的.class文件
- 从jar包中的.class文件
- 从远程网络中获取
- 动态代理实时编译
类加载器
- 启动类加载器
- 扩展类加载器
- 应用类加载器
- 自定义类加载器
为什么会有自定义类加载器?
- 一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
- 另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。
2、验证
主要是为了保证加载进来的字节码是否符合虚拟机规范
- 对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
- 对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
- 对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。
- 对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
3、准备(默认初始化)
主要是为类变量分配内存,并且赋予默认初始值
特别需要注意,初值不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。
比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456
4、解析
将常量池内的符号引用替换为直接引用的过程。
两个重点:
- 符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
- 直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的引用;而实例方法,实例变量的直接引用则是从实例的头引用开始算起到这个实例变量位置的偏移量
举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
5、初始化(自定义初始化)
这个阶段主要是对类变量进行自定义的初始化,是执行类构造器的过程。其实就是根据程序员自己设置的值对类变量进行赋值
int a = 5;
在准备阶段就是为这个a变量分配内存空间,并且赋其默认值,也就是准备阶段a=0,因为int类型的默认值为0,然后在初始化阶段就是把程序员设置的值赋值给对应的类变量,所以把5赋值给a,a=5。
注意:类加载的几个阶段都只针对类变量,所以类变量以外的变量赋值不会在类加载过程中体现