1. 什么是类加载器,类加载器有哪些?
要想理解类加载器的话,务必要先清楚对于一个Java
文件,它从编译到执行的整个过程。
- 类加载器:用于装载字节码文件(
.class
文件) - 运行时数据区:用于分配存储空间
- 执行引擎:执行字节码文件或本地方法
- 垃圾回收器:用于对
JVM
中的垃圾内容进行回收
类加载器
JVM
只会运行二进制文件,而类加载器(ClassLoader
)的主要作用就是将字节码文件加载到JVM
中,从而让Java
程序能够启动起来。现有的类加载器基本上都是java.lang.ClassLoader
的子类,该类的只要职责就是用于将指定的类找到或生成对应的字节码文件,同时类加载器还会负责加载程序所需要的资源。
类加载器种类
类加载器根据各自加载范围的不同,划分为四种类加载器:
-
启动类加载器(
BootStrap ClassLoader
):该类并不继承
ClassLoader
类,其是由C++
编写实现。用于加载**JAVA_HOME/jre/lib
**目录下的类库。 -
扩展类加载器(
ExtClassLoader
):该类是
ClassLoader
的子类,主要加载**JAVA_HOME/jre/lib/ext
**目录中的类库。 -
应用类加载器(
AppClassLoader
):该类是
ClassLoader
的子类,主要用于加载**classPath
**下的类,也就是加载开发者自己编写的Java
类。 -
自定义类加载器:
开发者自定义类继承
ClassLoader
,实现自定义类加载规则。
上述三种类加载器的层次结构如下如下:
类加载器的体系并不是“继承”体系,而是委派体系,类加载器首先会到自己的parent
中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。
2. 什么是双亲委派模型?
如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就返回成功;只有父类加载器无法完成此加载任务时,才由下一级去加载。
3. JVM
为什么采用双亲委派机制?
(1)通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
(2)为了安全,保证类库API
不会被修改
在工程中新建java.lang
包,接着在该包下新建String
类,并定义main
函数。
public class String {
public static void main(String[] args) {
System.out.println("demo info");
}
}
此时执行main
函数,会出现异常,在类java.lang.String
中找不到main
方法。
出现该信息是因为由双亲委派的机制,java.lang.String
的在启动类加载器(Bootstrap classLoader
)得到加载,因为在核心jre
库中有其相同名字的类文件,但该类中并没有main
方法。这样就能防止恶意篡改核心API
库。
4. 说一下类装载的执行过程?
类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking
)。
类加载过程详解
1.加载
-
通过类的全名,获取类的二进制数据流。
-
解析类的二进制数据流为方法区内的数据结构(
Java
类模型) -
创建
java.lang.Class
类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
2.验证
验证类是否符合JVM
规范,安全性检查
- 文件格式验证:是否符合
Class
文件的规范(2)元数据验证- 这个类是否有父类(除了
Object
这个类之外,其余的类都应该有父类) - 这个类是否继承(
extends
)了被final
修饰过的类(被final
修饰过的类表示类不能被继承) - 类中的字段、方法是否与父类产生矛盾。(被
final
修饰过的方法或字段是不能覆盖的)
- 这个类是否有父类(除了
- 字节码验证
- 主要的目的是通过对数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量
比如:
int i = 3;
字面量:3
符号引用:i
3.准备
为类变量分配内存并设置类变量初始值
-
static
变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成 -
static
变量是final
的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成 -
static
变量是final
的引用类型,那么赋值也会在初始化阶段完成
4.解析
把类中的符号引用转换为直接引用
比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。
5.初始化
对类的静态变量,静态代码块执行初始化操作
-
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
-
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
6.使用
JVM
开始从入口方法开始执行用户的程序代码
-
调用静态类成员信息(比如:静态字段、静态方法)
-
使用
new
关键字为其创建对象实例
7.卸载
当用户程序代码执行完毕后,JVM
便开始销毁创建的Class
对象,最后负责运行的JVM
也退出内存。
5. 面试现场
5.1 什么是类加载器,类加载器有哪些?
JVM
只会运行二进制文件,而类加载器(ClassLoader
)的主要作用就是将字节码文件加载到JVM
中,从而让Java
程序能够启动起来。
常见的类加载器有4个:
第一个是启动类加载器(BootStrap ClassLoader
):其是由C++
编写实现。用于加载JAVA_HOME/jre/lib
目录下的类库。
第二个是扩展类加载器(ExtClassLoader
):该类是ClassLoader
的子类,主要加载JAVA_HOME/jre/lib/ext
目录中的类库。
第三个是应用类加载器(AppClassLoader
):该类是ClassLoader
的子类,主要用于加载classPath
下的类,也就是加载开发者自己编写的Java
类。
第四个是自定义类加载器:开发者自定义类继承ClassLoader
,实现自定义类加载规则。
5.2 说一下类装载的执行过程?
类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking
)。
1.加载:查找和导入class
文件;
2.验证:保证加载类的准确性;
3.准备:为类变量分配内存并设置类变量初始值;
4.解析:把类中的符号引用转换为直接引用;
5.初始化:对类的静态变量,静态代码块执行初始化操作;
6.使用:JVM
开始从入口方法开始执行用户的程序代码;
7.卸载:当用户程序代码执行完毕后,JVM
便开始销毁创建的Class
对象,最后负责运行的JVM
也退出内存。
5.3 什么是双亲委派模型?
如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传说到顶层的启动类加载器中,只有当父类加载器返回自己无法完成这个加载请求(它的搜索返回中没有找到所需的类)时,子类加载器才会尝试自己去加载。
5.4 JVM
为什么采用双亲委派机制?
主要有两个原因。
第一、通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
第二、为了安全,保证类库API
不会被修改。