JVM类加载机制

来源:互联网 发布:淘宝开了店怎么找货源 编辑:程序博客网 时间:2024/05/16 02:26

总体流程

JAVA 加载一个类的流程如下:
JAVA源文件(.java)——>编译器——>字节码文件(.class)——>解释器(JVM的一部分)——>机器码

字节码文件经过JVM进行兼容处理可以在不同系统中得到同样的操作结果,这是JAVA跨平台的基础。

JVM虚拟机总是和程序对应的,程序启动则实例化虚拟机,程序结束则虚拟机消亡,不同的程序对应不同的虚拟机实例,且实例之间数据并不共享。

Jvm的体系结构

JVM体系结构

class字节码文件就是通过类装载器加载到JVM内存中进行处理的。

类装载器工作流程

类装载器的工作流程分为7个阶段,分别是装载、验证、准备、解析、初始化、使用、卸载,其中验证、准备、解析被统一称为连接。

其中加载、验证、准备、初始化这几个阶段是固定的,而解析则不一定,可能发生在运行期间(动态绑定)

加载

1.通过“类全名”获取定义此类的二进制字节流。
2.将字节流中静态存储结构转化为方法区中的运行时数据结构。
3.在JAVA堆中生成代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。
此外,还有其他渠道可以获取这些数据,如从jar包或war包读取,动态代理生成,其他文件生成(jsp)。
该阶段是整个类加载过程中可控性最强的阶段。该阶段使用系统提供的类加载器(ClassLoader)完成,也可以使用自定义类加载器完成。开发人员通过自己的加载器控制字节流的获取方式。

验证

验证阶段主要用于确保class文件中的字节流不会对虚拟机产生危害。主要包括四个验证过程:文件格式、元数据、字节码、符号引用
1、文件格式验证
验证class文件格式,是否符合虚拟机要求。如版本号是否在虚拟机处理范围之内。
2、元数据验证
对字节码中的信息进行语义分析,以保证描述的信息符合java语言规范要求。如该类是否有父类,父类是否允许继承,是否实现了接口等。
3、字节码验证
进行数据流和控制流分析,分析校验类的方法体,确保方法在运行时不会做出危害虚拟机安全的行为。如类型转换是否可行、跳转命令是否跳出方法体外等。
4、符号引用验证
验证是否可以通过符号引用中通过字符串描述的全限定名找到对应的类、符号引用类中的类、字段的访问权限是否通过等。

准备

准备阶段是正式为类中变量分配内存并初始化类变量的阶段。这些内存都由方法区提供。此时的类变量指得是static修饰的静态变量,而不是对象实例中的变量,实例变量会随对象分配在java堆中。准备阶段的类变量不会是程序中设定的值,而是默认为0。真正的赋值阶段在初始化中。然而使用ConstantValue属性的变量会在该阶段赋值。

解析

该阶段是虚拟机常量池中的符号引用替换为直接引用的过程。
符号引用:用一组符号描述所引用的对象,只要能准确定位到目标即可。
直接引用:直接指向对象在内存中的地址(指针、偏移量、句柄都可)。

解析阶段并没有具体的事件,只要是遇到特定字节码(invoke、instanceof),在此之前会对他们的符号引用进行解析。解析动作主要针对的是类/接口、字段、类方法、接口方法。

初始化

类的初始化阶段是类加载的最后一步,在准备阶段,类变量(static)以及赋值过一次系统要求的初始值。该阶段则是根据开发人员程序中所指定的值进行赋值。
触发初始化的情况:
1、使用new实例化对象,读取/设置静态字段(final除外),调用类的静态方法。
2、对类进行反射调用
3、初始化类时,先初始化未初始化的父类。
4、虚拟机启动时,用户指定的主类(main方法类),先初始化该类。

类装载器

JVM设计者将类加载阶段中“通过 类全名 来获取定义此类的二进制字节流”过程交由jvm外部的代码模块实现,让应用程序决定如何获取自己所需的类。这个代码模块称为“类加载器”。

类与类加载器

一个类的唯一性由类本身和类加载器共同决定的。同一个Class文件由不同类加载器进行加载,生成的类并不能认为是同一个类。

双亲委派模型

从虚拟机的角度来看,只存在两种类加载器,即启动类加载器(BootstrapClassLoader,由C++编写,虚拟机的一部分)和其他类加载器(由JAVA语言编写,独立于虚拟机,全部继承自抽象类java.lang.ClassLoader)。

从开发人员角度看,大部分java程序都使用三种系统提供的类加载器,分别是:
1、启动类加载器(BootstrapClassLoader):加载JAVA_HOME\lib目标下被JVM所识别的类库。无法被java程序直接引用。
2、扩展类加载器(ExtensionClassLoader):加载JAVA_HOME\lib\ext中的类库,可以为开发者所使用。
3、应用程序类加载器(ApplicationClassLoader):也称为系统类加载器,负责加载用户类路径(ClassPath)上所指的的类库,开发者可以直接使用的加载器,无自定义加载器时默认使用。

类加载器的关系级别为:
启动类加载器>扩展类加载器>应用程序加载器>自定义加载器

这种层次关系就成为双亲委派模型。除启动类加载器外,其他类加载器都应有自己的父类加载器,通过组合(Comosition)关系复用父加载器代码。

基本工作过程为:
1、某个类收到类加载请求,将加载请求提交给父类。
2、父类加载器依次向上层加载器提交请求,直至启动类加载器。
3、若父类加载器反馈无法完成该请求(加载器搜索范围未找到对应的类),子类加载器尝试自己去加载。

好处:java类随着类加载器存在一种优先级关系。系统的关键类不会因为用户使用同样的类名加载错误,防止程序因为出现多个同名类而出现混乱。

面试问题:能否自己写一个与系统类重名的类。
通常不可以,但是通过自定义类加载器并将该加载器放在系统三个加载器加载不到的地方,以脱离双亲委托机制。系统类无法完成加载时,由自定义加载器完成。