类加载机制
一个类的加载生命周期分为下面几步,加载,连接(验证、准备、解析),初始化,使用,卸载四个过程(七个过程),其中 加载、验证、准备、初始化和卸载 这五个阶段的顺序是确定的,解析不一定在准备的后面,为了支持运行时绑定的特性,也有可能在初始化之后进行解析。
1、类加载过程
1.1 加载
加载阶段虚拟机主要做三件事:
通过一个类的全限定名来获取定义此类的二进制字节流,这个宽泛的规则为文件的来源提供了舞台,可以从压缩文件、网络、加密数据运行时生成等入口获取;
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生成一个代表这个类的
java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
类加载器可以使用 Java 虚拟机里内置的引导类加载器,也可以由用户自定义的类加载器(重写一个类加载器的findClass()或loadClass()方法),实现根据自己的想法来赋予应用程序获取运行代码的动态性。
1.2 验证
确保 Class 文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全,其又分为四个验证步骤:
文件格式验证:字节流是否符合Class文件格式的规范,是否能被当前版本的虚拟机处理(向下兼容);
元数据验证:对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相悖的元数据信息(父类继承、抽象实现等等);
字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的;
符号引用验证:检查是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。
1.3 准备
正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段(初始值也并非代码中的定义值,而是该类型的默认初始值),如果是被 final 给修饰的 constanValue 则会直接赋值为定义值。
1.4 解析
将常量池内的符号引用替换为直接引用的过程:
符号引用(内存无关):以一组符号(任何形式的字面量)来描述所引用的目标,只要使用时能无歧义地定位到目标即可。
直接引用(内存有关):直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄,目标已经在虚拟机内存中实例化存在。
1.5 初始化
该阶段虚拟机开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。
在准备阶段,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源(<clinit>()),方法编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的(静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,访问会报错非法向前访问)。
同时 <clinit>() 的执行从父类开始,也就是静态代码的执行顺序是从父类到子类(并且,同一个类加载器下面的类初始化只会执行一次)。
2、类加载器
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,包括不限于对于代表类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果对比。
2.1 双亲委派模型
站在开发者的角度,Java 一直保持着三层类加载器、双亲委派的类加载架构:
启动类加载器:负责加载存放在
<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是 Java 虚拟机能够识别的(按照文件名识别,如 tools.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机的内存中。该加载器不能直接被程序引用,但可以通过用户的自定义加载器返回 null 来显示表达需要使用启动类加载器。拓展类加载器:负责加载
<JAVA_HOME>\lib\ext目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库,其在类 sun.misc.Launcher$ExtClassLoader 中以 Java 代码的形式实现的,因此可以直接通过程序引用,作用是用来拓展 SE 的功能,但是被 Java 的模块所取缔。应用程序类加载器:负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器(如果没有自定义类加载器,那么默认就是这个)。
除了上述的三层加载器外,还有用户自定义的加载器,所有加载器按照启动、拓展、应用、自定义的顺序进行使用,以一种组合关系,底层的加载器如果收到了类加载的请求,不会直接去加载,而是通过 父类加载器 去加载,如果上级不能处理,自己才会去加载,这样的模式就叫做双亲委派模型。该种架构的一种好处就是类具有了层级关系,避免了一些覆盖问题。
2.2 破坏双亲委派模型
双亲委派的机制是 ClassLoader 中的 loadClass 方法实现的,打破双亲委派,其实就是重写这个方法,来用我们自己的方式来实现即可。
如果不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。
而如果想打破双亲委派模型则需要重写 ClassLoader 类 loadClass() 方法。
3、模块化系统
- 感谢你赐予我前进的力量