类加载流程
- 类加载器子系统负责从文件系统或者网络中加载class文件,class文件的文件头有特定的文件标识(CAFEBABE是JVM识别class文件是否合法的依据)
- classLoader只负责文件的加载,而执行引擎决定它是否被执行
- 加载类的信息存放在运行时数据区的方法区中,方法区还包括常量池信息
类加载过程由加载,链接,初始化构成
加载(Loading)
加载阶段需要完成以下三个过程:
- 通过类的全限定名来获取其定义的二进制字节流;
- 将字节流所代表的静态存储结构转化为元空间(JDK8前称为方法区)的运行时数据结构;
- 在堆Heap中生成一个代表这个类的java.lang.Class对象,作为对元数据空间中这些数据的访问入口;
类加载器
引导类加载器
Bootstrap-ClassLoader基于C/C++实现,负责加载Java的核心类库JAVA_HOME\jre\lib\rt.jar,该加载器不继承自ClassLoader抽象类,并且只加载包名为java、javax、sun等开头类,一次保证对核心源码的保护
扩展类加载器
Extension-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,派生于ClassLoader抽象类,从java.ext.dirs系统变量指定的路径中的加载类库,或者JDK安装目录jre\lib\ext目录下加载
系统类加载器
Application-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,它负责加载环境变量ClassPath指定的类库,如果在应用程序中没有自定义类加载器,一般情况下作为程序中默认的类加载器
自定义类加载器
主要作用
- 隔离加载类(如项目工程中的中间件需要引入自己jar包,为了避免与其他工程具有相同类的冲突,我们可以自定义类加载器,实现不同中间件的隔离)
- 修改类加载方式
- 扩展加载源
- 防止源码泄漏
扩展
- sun.misc.Launcher是java虚拟机的入口应用,扩展类和系统类(App)定义在Launcher类下
- 加载器之间的关系只是包含关系,不是上层下层,也不是父子类的继承关系
双亲委派机制
双亲委派模式
Java虚拟机对class文件采用的是按需加载方式,即当需要使用该类时才会将它的class文件加载到内存生成class对象,而且在加载某个类class文件时,Java虚拟机采取的是双亲委派模式,即将请求交由父类处理,它是一种任务委派模式
委派执行流程
- 类加载器收到了类加载的请求时,不会自己先去尝试加载这个类,而是把请求委托给父加载器去执行
- 如果父加载器还存在父类加载器,则依次向上委托,因此类加载请求最终都应该被传递到顶层的启动类加载器中
- 如果父类加载器可以完成类加载请求,就直接成功返回,只有当父加载器在无法完成该加载,子加载器才会尝试自己去加载该类
沙箱安全机制
假设自定义一个类名为String且所在包为java.lang,在使用引导类加载器加载时会先加载JDK(rt.jar包下的java/lang/String.class)中的String类,因为这个类本来是属于jdk的,后面再次出现String类就会报错,以此保证核心源代码不被恶意篡改,这就是沙箱安全机制。
链接(Linking)
执行流程依次为验证->准备->解析
验证
其目的在于确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证加载类的正确性,不会危害虚拟机自身的安全
验证内容:
- 文件格式验证:验证字节流是否符合Class文件格式的规范
- 元数据验证:确保其描述的信息符合Java语言规范的要求
- 字节码验证:确定程序语义符合逻辑
- 符号引用验证:确保解析动作能正确执行
准备
为类的静态变量分配内存,并初始化为默认值,仅包括类变量(static修饰,不包括final-static修饰),这里也不会为实例变量分配初始化,实例变量会随着对象一块分配到Java堆内存中
比如:某类中定义一个静态变量:private static int i = 10;此时在准备阶段i = 0,在后续的初始化阶段才会被赋值为10
解析
将常量池中的符号引用转换为直接引用的过程,直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。解析主要针对类或接口、字段、类方法、接口方法、方法类型等,解析的动作实际是会随着JVM在执行完初始化之后再执行的。
初始化(Initialization)
- 初始化就是执行类构造器clinit()方法的过程
- 该方法不需要自定义,是javac编译器自动收集类中的所有类变量的显示赋值动作和静态代码块中的语句合并而来
- 构造器方法中你指令按语句在源文件中出现的顺序执行
- JVM要保证clinit()方法在多线程访问下的安全性,即被同步加锁