说到jvm类加载机制,就不得不说jvm语言的跨平台性,jvm语言如java,除了因为其面向对象的特性而著名外,还因为其一次编译,到处运行的特性让人眼前一亮。
传统的计算机语言一般而言都是经过编译器将代码转化为机器码,然后直接使用机器语言运行。这个就不展开讲利弊了,jvm为了解决这种需要到处编译,然后解决编译问题的痛苦。在代码和机器码中间,加了一层过渡层,就是字节码,字节码很好的解决了不同系统和不同硬件造成的兼容问题。字节码采用统一规范,然后由不同系统上的jvm虚拟机将字节码转化为机器码,再运行。
所以其实上jvm和其上运行的语言无关,只与语言编译器编译后的字节码有关,如果编译器编译后的字节码足够高效,其运行效率或者其他性能也就越强。
进入正题,jvm类加载主要是由五个大大阶段组成,其中连接阶段又有三个小阶段,但要注意的是,随着动态编译等需求,在连接阶段中,会发生一些变化,其中初始化开始后,可能解析过程才会开始,具体的就不多介绍了,了解一下总体流程及各个过程中做的事情。
加载过程简单来讲就是将类的一些基础信息加载到内存中去,主要是三个,第一个是类的全限定名,第二个是生成一个代表这个类的class对象,然后放入到方法区中,第三个就是将静态存储结构转化为方法区的运行时数据结构。(不太懂)
验证过程对class文件的安全性进行检验。
文件格式检验:这个过程基本上不会有问题,主要检验class文件在编译后是否有损毁。
元数据验证:同上游类验证,检验父类,接口以及其中的方法是否有冲突。
字节码验证:检查字节码的语义逻辑,比如强转是否符合要求
符号引用验证:字节码中存放的引用都是符号引用,当符号引用读入内存后,会将其转换为内存中数据的地址,既直接引用。符号引用验证就是为了验证符号引用是否可达,是否符合规范,能否被访问等
准备是为了给变量分配内存以及初始化值
很多变量都有初始值,在java中,相信不少人都有体验,当不给变量赋值时,变量也会有值可以使用,比如布尔值的默认是只false,int的初始值是0。
解析是最为复杂也是最耗时的一个过程,主要包括类或接口的解析、字段解析、类方法解析、接口方法解析
解析动作主要针对类或接口、 字段、 类方法、 接口方法、 方法类型、 方法句柄和调用点限定符7类符号引用
类或接口的解析:
解析步骤例子:类D,解析符号引用N为C(类或接口)
1、把代表N的全限定名给D的类加载器,加载C(不是数组),会触发其他相关类的加载动作
2、如果C是一个数组类型,jvm生成一个代表此数组维度和元素的数组对象。
3、最后进行权限确认。
字段解析:
1、如果在类中直接找到了简单名称和字段描述都相匹配的字段,返回直接引用,查找结束。
2、在C中实现了接口,按照继承关系从下往上递归搜索各个接口和父接口
3、如果C不是java.lang.Object(最终父类),将会按照继承关系从下往上递归搜索父类
4、抛出NoSuchFieldError异常
5、进行权限验证
注意:如果一个字段同时出现在同级的接口和父类中,会拒绝编译
类方法解析:
1、检查:方法不能为接口,抛出java.lang.IncompatibleClassChangeError
2、在方法中查找简单名称和描述符都与目标匹配的方法
3、在类C的父类中递归查找是否有简单名称和描述符都与目标匹配的方法
4、在类C实现的接口列表 及父接口中递归查找是否有简单名称和描述符,有,则说明是一个接口,抛出异常java.lang.AbstractMethodError
5、抛出java.lang.NoSuchMethodError
6、权限验证
接口方法解析
1、检查:方法不能为类,抛出异常java.lang.IncompatibleClassChangeError
2、在接口C中找
3、在父接口中递归查找
4、抛异常,java.lang.NoSuchMethodError
不存在权限问题,所以不需要检查
初始化是执行clinit方法的过程,会收集类中的所有变量的赋值动动和静态语句块的语句,需要注意的是,这个过程是逐句翻译的,