概述
将.java文件编译成.class字节码文件的过程是编译期。
从.class
文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java
类型的过程就是运行期,也就是标题所说的类加载过程。
(红色框代表的就是类加载过程)
具体过程
- 『加载』->『验证』->『准备』->『初始化』->『卸载』这五个阶段的顺序是确定的,而『解析』可能为了支持
Java
的动态绑定会在『初始化』后才开始 - 上述阶段通常都是互相交叉地混合式进行的,比如会在一个阶段执行的过程中调用、激活另外一个阶段
1、加载
- 通过类的全限定名来获取定义此类的二进制字节流。如从
ZIP
包读取、从网络中获取、通过运行时计算生成、由其他文件生成、从数据库中读取等等途径 - 将该二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义
- 在内存中生成一个代表这个类的
java.lang.Class
对象,它将作为程序访问方法区中的这些类型数据的外部接口
2、验证
- 是连接阶段的第一步,且工作量在
JVM
类加载子系统中占了相当大的一部分 - 目的:为了确保
Class
文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全 - 可以考虑使用
-Xverify:none
参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 - 包括四个阶段验证:
- A:文件格式验证:验证字节流是否符合
Class
文件格式的规范、以及是否能被当前版本的虚拟机处理 - B:元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合
Java
语言规范的要求(是否有父类,是否继承了不该继承的类,如final,如果不是抽象类是否实现了所有方法) - C:字节码验证:对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件,最复杂的一个阶段。
- D:符号引用验证:对类自身以外(如常量池中的各种符号引用)的信息进行匹配性校验
3、准备
- 为类变量(静态变量)分配内存:因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一起分配在
Java
堆中 - 设置类变量初始值:通常情况下零值
4、解析
解析阶段就是虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用:以一组符号来描述所引用的目标
- 可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
- 与虚拟机实现的内存布局无关,因为符号引用的字面量形式明确定义在
Java
虚拟机规范的Class
文件格式中,所以即使各种虚拟机实现的内存布局不同,但是能接受符号引用都是一致的
- 直接引用:
- 可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄
- 与虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不同
- 发生时间:
JVM
会根据需要来判断,是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析
5、初始化
- 是类加载过程的最后一步,会开始真正执行类中定义的
Java
代码。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制
类加载器
每个类加载器,都拥有一个独立的命名空间,它不仅用于加载类,还和这个类本身一起作为在JVM
中的唯一标识。所以比较两个类是否相等,只要看它们是否由同一个类加载器加载,即使它们来源于同一个Class
文件且被同一个JVM
加载,只要加载它们的类加载器不同,这两个类就必定不相等
从JVM
的角度,可将类加载器分为两种:
- 启动类加载器
- 由
C++
语言实现,是虚拟机自身的一部分- 负责加载存放在
<JAVA_HOME>\lib
目录中、或被-Xbootclasspath
参数所指定路径中的、且可被虚拟机识别的类库- 无法被
Java
程序直接引用,如果自定义类加载器想要把加载请求委派给引导类加载器的话,可直接用null
代替
- 其他类加载器:由
Java
语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader
,可被Java
程序直接引用。常见几种:
扩展类加载器
A.由
sun.misc.Launcher$ExtClassLoader
实现B.负责加载
<JAVA_HOME>\lib\ext
目录中的、或者被java.ext.dirs
系统变量所指定的路径中的所有类库应用程序类加载器
A.是默认的类加载器,是
ClassLoader#getSystemClassLoader()
的返回值,故又称为系统类加载器B.由
sun.misc.Launcher$App-ClassLoader
实现C.负责加载用户类路径上所指定的类库
自定义类加载器:如果以上类加载起不能满足需求,可自定义
注:虽然数组类不通过类加载器创建而是由JVM
直接创建的,但仍与类加载器有密切关系,因为数组类的元素类型最终还要靠类加载器去创建。
双亲委派模型
若一个类加载器收到了类加载的请求,它先会把这个请求委派给父类加载器,并向上传递,最终请求都传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载
委托机制的意义 — 防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:
- 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
- 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。