1.JVM类加载的五个过程
- 加载
- 验证
- 准备
- 解析
- 初始化
A.加载
加载有几步:
- 通过类的全限定名获取该类的字节流
- 在内存中生成一个这个类的Class对象
- 将流储存到方法区中
所加载类的来源有许多种:从Class文件中获取,也可以从jar或war包中获取,又或者在运行时通过动态代理生成,也可以从网络中获取,当然也可以是由JSP文件转换为对应的Class类。
B.验证
JVM会验证刚刚加载的类是否符合虚拟机的要求,是否有正确的内部结构,同时也是为了保护虚拟机。
主要包括四种验证:
- 文件格式的验证:验证class文件是否符合正确格式,例如版本号是否是这个JVM能处理的,常量类型是否支持。
- 元数据验证:字节码是否符合Java语言语法规范,例如父类是否继承了不允许被继承的类。
- 字节码验证:验证类的方法体是否正确
- 符号引用验证:引用是否能找到正确的目标,即验证是否能通过引用访问到对象。
C.准备
在方法区中为类变量(static)分配内存并根据类型设置对应的默认值。
需要注意的是,这里的默认值不是指显式赋予的值,显示赋值在之后初始化时赋予,例如
private static int i = 5;
默认值不是这个5而是0,不同的数据类型有不同的默认值:
数据类型 | 默认值 |
---|---|
int | 0 |
long | 0L |
short | 0(short) |
boolean | false |
float | 0.0f |
double | 0.0d |
但是如果类变量被final和static同时修饰了,在准备阶段就会赋予显式赋值的初始值
例如
private static final int i = 5;
D.解析
在解析阶段,JVM会将常量池中的符号引用替换为直接引用。别处看到的有关符号引用和直接引用的解释:
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在
可以理解为,符号引用就是一个昵称,不会冲突,我们能通过昵称知道这个人是谁;而直接引用就是一个指向这个人的指针,这个人一定在指针所指在的地址。
E.初始化
这是类加载的最后一个阶段,这个阶段执行类构造器 < client >()方法,会为类的静态变量赋予正确的初始值,并调用静态代码块。
在通过new创建对象、访问类的静态变量或方法、通过Class.forName加载类时,都会执行类的初始化。
2.JVM类加载器
类加载器(ClassLoader)负责将类加载到JVM中,而且还会判断每个类应该由什么加载器来加载。
A.ClassLoader类
ClassLoader类中有一个方法
defineClass(byte[] b, int off, int len)
这个方法可以将传入的byte字节流解析为JVM能识别的Class对象,正是因为有了这个方法,我们在加载阶段可以有多种所加载类的来源而不限于Class文件。
B.类加载器 jdk8
JVM提供了三种类加载器:
- Bootstrap ClassLoader
- Extension ClassLoader
- Application ClassLoader
Bootstrap ClassLoader
Bootstrap ClassLoader负责加载JAVA_HOME\lib中的被虚拟机认可的类,或者通过-Xbootclasspath参数指定路径的类。
Extension ClassLoader
Extension ClassLoader负责加载JAVA_HOME\lib\ext目录中的类,或者通过java.ext.dirs系统变量指定路径中的类。
Application ClassLoader
Application ClassLoader负责加载用户路径(classpath)上的类库,我们也可以继承java.lang.ClassLoader实现自定义的类加载器。
补充
在jdk10中,使用PlatformClassLoader来替代Extension ClassLoader。
D.双亲委派机制
ClassLoader的加载机制是一个向上级委托的机制,当一个类需要被加载的时候,会将这个请求委派给父类加载器,并进行判断:
- 如果父类加载器不能完成对这个类的加载,那么该加载器就会对类进行加载
- 如果父类加载器可以完成这个请求,那么父类加载器也会同样的向更上一级的加载器去请求,每一层的加载器都会这么做,直到上一级不能完成这个请求为止。
假设类A有两个子类类B和类C,且类B和类C分别由不同的类加载器加载,采用双亲委派机制,就保证了无论通过类B还是类C去加载父类A,都会委托给类A对应的类加载器加载,保证得到的是一样的类A。
最简单的例子就是无论什么加载器加载Object类,通过这个机制,都会委托给顶层的Bootstrap ClassLoader去加载,保证得到同一个Object。
3.类加载异常
类加载有几个常见的异常:
- ClassNotFoundException:使用显式加载类的时候,如Class的forName方法,未能通过类的名字找到对应的类,就会抛出这个异常
- ClassCastException:类型转换错误的异常