1、类加载机制概述
我们知道,一个.java文件在编译后会形成相应的一个或多个Class文件(若一个类中含有内部类,则编译后会产生多个Class文件),这些Class文件中描述了类的各种信息,并且它们最终都需要被加载到虚拟机中才能被运行和使用。
事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程就是虚拟机的类加载机制。
JVM类加载机制主要包括两个问题:
- 类加载的时机与步骤
- 类加载的方式(虚拟机如何加载一个Class文件)(类加载器、双亲委派机制)
一个Java对象的创建过程往往包括两个阶段:
- 类初始化阶段
- 类实例化阶段 (类的实例化是指创建一个类的实例(对象)的过程)
2、类加载的时机
Java类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 卸载(Unloading)七个阶段。其中准备、验证、解析3个部分统称为连接(Linking),如图所示:
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。
特别需要注意的是,类的加载过程必须按照这种顺序按部就班地“开始”,而不是按部就班的“进行”或“完成”,因为这些阶段通常都是相互交叉地混合式进行的,也就是说通常会在一个阶段执行的过程中调用或激活另外一个阶段。
3、类加载过程
java 虚拟机中类加载的全过程 : 加载 验证 准备 解析 初始化
加载
1. 通过一个类的全限定名来获取定义此类的二进制字节流2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构3. 在内存中生成一个代表这个类的Java.lang.class 对象, 作为方法区这个类的各种数据的访问入口
验证
这一阶段的目的是确保 Class 文件的字节流中包含的信息符合 <Java 虚拟机规范 > 的全部约束要求, 确保这些信息被当做代码运行后不会危害虚拟机自身的安全

-
文件格式验证 : 验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理
- 是否以魔数 开头
- 主 次版本号是否在当前 Java 虚拟机接收范围之内
- 常量池的常量中是否有不被支持的常量类型
- ……
-
元数据验证
- 这个类是否有父类 ( 除了 java.lang.Object之外, 所有的类都应当有父类)
- 这个类是否继承了不允许被继承的类 (被final 修饰的类)
- 如果这个类不是抽象类, 是否实现了其父类或接口之中要求实现的所有方法
- ……
-
字节码验证 : 对类的方法体( Class文件中的 Code 属性) 进行校验分析,保证被校验类的方法在运行是不出危害虚拟机安全的行为
- 保证任何跳转指令都不会跳转到方法体以外的字节码指令上.
- 保证在任意时刻操作数栈的数据类型与指令代码序列都能配合工作
- ……
-
符号引用验证
- 符号引用中通过字符串描述的类全限定名是否能找到对应的类
- 符号引用中的类 字段 方法的可访问性 (public private) 是否可被当前类访问
- ……
准备
准备阶段是正式为类中定义的变量 (即静态变量,被 static修饰的变量) 分配内存并设置类变量初始值的阶段 .
解析
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程 .
- 类和接口的解析
- 字段解析
- 方法解析
初始化
到初始化阶段, Java 虚拟机才真正开始执行类中编写的 Java 程序代码, 将主导权交给应用程序
4、类加载器
类加载器只用于实现类的加载动作
双亲委派模型
JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader:
- BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载
%JAVA_HOME%/lib目录下的jar包和类或者或被-Xbootclasspath参数指定的路径中的所有类。 - ExtensionClassLoader(扩展类加载器) :主要负责加载目录
%JRE_HOME%/lib/ext目录下的jar包和类,或被java.ext.dirs系统变量所指定的路径下的jar包。 - AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。

双亲委派模型要求除了顶层的启动类加载器, 其余的类加载器都应有自己的父类加载器
双亲委派模型的工作过程 : 如果一个类加载器收到了类加载请求, 它首先不会自己去尝试加载这个类, 而是把这个请求委托给父类加载器完成, 每一层次的类加载器都是如此, 因此所有的类加载请求最终都应该传到最顶层的启动类加载器, 只有当父类加载器反馈自己无法完成这个加载请求( 他的搜索范围没有找到所需的类 ), 子加载器才会尝试自己去完成加载 .
双亲委派模式优势
避免重复加载 + 避免核心类篡改采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心JavaAPI发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。