1、JVM 的类加载机制
类加载指的是 Java 进程运行的时候,需要把 .class 文件从硬盘读取到内存,并进行一系列的校验解析的过程。
类加载的过程其实就是 .class 字节码文件转成 类对象 的过程,本质上也是数据从硬盘到内存的过程。
类加载大体的过程分为 5 个步骤(也有资料说 3 个,其实就是将 2,3,4 合并成 1 个):
1.1、加载
找到硬盘上的 .class 文件(使用双亲委派模型,下文将讲解),打开文件并读取文件内容(认为读到的是二进程数据)。
1.2、验证
验证当前读到的文件内容合法的 .class 字节码文件格式,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。具体的验证依据根据,在 Java 的虚拟机规范中有明确的格式说明。
虚拟机规范中的部分截图
1.3、准备
给类对象申请内存空间,此时申请到的内存空间都是默认值全0。
1.4、解析
解释阶段主要是针对类中的字符串常量进行处理,Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
private String s = "hello";
上述代码中很显然,s 变量相当于保存了“hello”字符串常量的地址,但是在文件中,不存在“地址”的概念,地址是“内存”的地址,因此在硬盘的文件中使用类似地址的“偏移量”来表示。
而文件中给 s 填充的“hello”偏移量就可以认为是“符号引用”;
接下来将 .class 文件加载到内存后,会将字符串加载到内存中,此时“hello”就有地址了,就可以将当前“hello”真实的地址替换“偏移量”,称为“直接引用”;
1.5、初始化
针对类对象完成后续的初始化,还要执行静态代码块的逻辑,还可能触发父类的加载。
2、双亲委派模型
上述1.1、加载步骤中提到了要先从硬盘上找到 .class 文件,那么如何找呢?此时就需要使用到“双亲委派模型”,JVM 中有一个专门的模块“类加载器”(ClassLoader)来进行类加载的操作,即通过带有包名的类名(如 java.lang.String)来找到对应的 .class 文件。
JVM 中的 “类加载器” 默认有三个:
1、BootstrapClassLoader
负责查找标准库的目录。
2、ExtensionClassLoader
负责查找扩展库的目录。Java 语法规范里面描述了标准库中应该有哪些功能,但是实现 JVM 的厂商/组织也会在标准库的基础上扩充一些额外的功能库(不同厂商实现的 JVM 也可能有所不同)。
3、ApplicationClassLoader
负责查找当前项目的代码目录,以及第三方库的目录。
2.1、工作过程
1、从 applicationclassloader 作为入口,先开始工作。applicationclassloader不会立即搜索自己负责的目录,会把搜索的任务交给自己的父亲 extensionclassloader。
2、此时代码就进入到 extensionclassloader 范畴了,extensionclassloader也不会立即搜索自己负责的目录,也要把搜索的任务交给自己的父亲 bootstrapclassloader。
3、bootstrapclassloader 发现自己没有父亲,才会真正搜索负责的标准库目录,通过全限定类名,尝试在标志库中找到符合要求的 .class 文件,如果找到了,接下来就直接进入到打开文件/读文件等流程中,如果没找到则会返回到 extensionclassloader ,继续尝试加载,依此类推。
4、如果最后走到 applicationclassloader 依然没找到,此时说明类加载过程失败,就会抛出 classnotfoundexception 异常。