面试题答案
一键面试Java虚拟机的类加载过程
- 加载(Loading):
- 查找并加载类的二进制数据。Java虚拟机可以从不同来源加载类,比如本地文件系统、网络等。例如,从本地文件系统加载
.class
文件。 - 对于数组类,它不是由类加载器直接加载,而是由Java虚拟机在运行时动态生成。
- 在加载阶段,会创建一个代表该类的
java.lang.Class
对象,作为方法区中该类的各种数据的访问入口。
- 查找并加载类的二进制数据。Java虚拟机可以从不同来源加载类,比如本地文件系统、网络等。例如,从本地文件系统加载
- 验证(Verification):
- 文件格式验证:验证字节流是否符合
.class
文件格式的规范,比如是否以魔数0xCAFEBABE
开头,主次版本号是否在当前Java虚拟机接受的范围内等。 - 元数据验证:对类的元数据信息进行语义校验,比如这个类是否有父类(除了
java.lang.Object
),类中的字段、方法是否与父类冲突等。 - 字节码验证:确保方法体中的字节码是合法、合理的,不会出现诸如跳转到方法体以外位置等问题。
- 符号引用验证:确保解析阶段能正确将符号引用转换为直接引用,比如符号引用指向的类、字段、方法是否存在等。
- 文件格式验证:验证字节流是否符合
- 准备(Preparation):
- 为类的静态变量分配内存并设置默认初始值。例如,对于
public static int value = 10;
,在准备阶段会为value
分配内存并初始化为0(默认值),而不是10。只有在初始化阶段才会将其赋值为10。 - 对于
public static final int value = 10;
这种常量,在准备阶段就会直接赋值为10,因为常量在编译期就已经确定其值。
- 为类的静态变量分配内存并设置默认初始值。例如,对于
- 解析(Resolution):
- 将常量池中的符号引用替换为直接引用。符号引用是一种间接的引用,在编译时由一组符号来描述所引用的目标,比如类的全限定名、方法名等。直接引用是可以直接指向目标的指针、相对偏移量或者能直接定位到目标的句柄。
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。例如,当解析一个类的符号引用时,会检查这个类是否已经被加载,如果没有则进行加载。
- 初始化(Initialization):
- 执行类构造器
<clinit>()
方法。<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。 - 虚拟机会保证一个类的
<clinit>()
方法在多线程环境下被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要等待,直到活动线程执行完毕<clinit>()
方法。
- 执行类构造器
双亲委派模型的工作原理
- 模型结构:双亲委派模型的结构是由启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)组成,它们之间形成一种层次关系,即父子关系。
- 工作流程:当一个类加载器收到类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去加载。例如,应用程序类加载器收到加载某个类的请求,它会先委托扩展类加载器,扩展类加载器再委托启动类加载器。如果启动类加载器找不到,扩展类加载器才尝试加载,若扩展类加载器也找不到,应用程序类加载器才自己加载。
双亲委派模型对Java系统安全性的影响
- 防止核心类被篡改:由于核心类(如
java.lang.Object
等)是由启动类加载器加载的,而启动类加载器加载的类都存放在rt.jar
等核心库中。其他类加载器无法重新加载这些核心类,即使在自定义类路径下有同名类,也不会被加载,从而保证了核心类的安全性和稳定性,防止恶意代码替换核心类。 - 保证类的唯一性:双亲委派模型使得类在整个Java虚拟机中有唯一的加载方式。比如在不同的类加载器环境下,相同全限定名的类只会被父类加载器加载一次,这样可以避免因重复加载导致的类不一致问题,增强了系统的安全性和稳定性。例如,在一个Web应用中,不同的Web应用可能有相同全限定名的类,但由于双亲委派模型,它们不会被重复加载,而是共享由父类加载器加载的类。