[db:标题]
摘要:A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
摘要
A公司的面经
JVM的类加载的过程是怎么样的?
双亲委派模型的优点和缺点?
产生fullGC的情况有哪些?
spring的动态代理有哪些?区别是什么?
如何排查CPU使用率过高?
JVM的类加载的过程是怎么样的?
这个问题有些抽象,是指要说出具体步骤,还是要深入每一步的细节?再次确认一下范围,给出的回答是,你自己了解多少就说多少。这就有意思,那我就凭自己的语言进行总结发挥了。
简述
类加载,是指JVM将.class文件的数据加载到内存中,并进行校验、解析以及初始化等一系列操作后,最终生成可被JVM直接使用的数据的过程。
解释
我们知道一个类在JVM的生命周期大致可以分为7个阶段:加载、验证、准备、解析、初始化、使用、卸载。
类加载的过程,主要就是类生命周期的前5个阶段,所以类加载的主要步骤为:
加载、验证、准备、解析、初始化。
因为【验证】、【准备】、【解析】有时候被统一称为链接阶段,因此有时候类加载也会被分三个步骤:加载、链接、初始化。
加载(Loading)
第一步,加载,主要是通过类的全限定名(如:java.lang.String)获取类的二进制字节流,将字节流转换为JVM运行时的数据结构,在堆中生成一个 java.lang.Class 对象,作为该类的访问入口。
触发方式:
ClassLoader.getSystemClassLoader().loadClass("com.jimoer.Test")
Class.forName("com.jimoer.Test") // 加载并初始化
创建实例(new Test())、调用静态方法或访问静态字段。
验证(Verification)
主要是校验,.class文件的正确性。
校验类的正确性(文件格式,元数据,字节码,二进制兼容性),保证类的结构符合JVM规范。
准备(Preparation)
为类的 静态变量(static 字段)分配内存并设置 默认值。
这里只初始化类变量,即static变量,所以都是在方法区里面进行分配内存的。而实例变量是会在对象实例化的时候进行初始化的,并在Java堆里分配内存。
解析(Resolution)
将常量池中的 符号引用 转换为 直接引用。
把类的符号引用转为直接引用(类或接口、字段、类方法、接口方法、方法类型、方法句柄和访问控制修饰符7类符号引用)。
初始化(Initialization)
执行类的 初始化逻辑(即 <clinit>() 方法),完成静态变量赋值和静态代码块的执行。
public class ClassInit {
static int a = 10; // 准备阶段:a = 0;初始化阶段:a = 10
static {
a = 20; // 最终 a = 20
}
}
双亲委派模型的优点和缺点?
Java应用是由 启动类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)、应用程序类加载器(Application Class Loader),这三类加载器互相配合来完成加载的,如果有自定义的类加载器,会先执行自定义的类加载器。
各种的类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
双亲委派模型的工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶端的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载才会尝试自己去完成加载。
双亲委派模型的优点
避免类的重复加载。确保了不同类加载器加载的相同类是同一个实例,避免类型冲突(如java.lang.Object的唯一性)。
防止恶意代码篡改核心类,以及避免因类版本不一致导致的兼容性问题。例如,攻击者无法通过自定义类加载器替换java.lang.String为恶意实现,从而保障JVM运行安全。
提高类加载效率。通过层级委托机制,减少重复搜索类路径(ClassPath)的次数。类加载器只需尝试一次父类加载器的加载,若失败再自行加载,避免了全盘扫描,提升性能。
双亲委派模型的缺点
限制自定义类的动态更新。一旦类被父类加载器加载(如BootStrapClassLoader),即使类文件被修改,子类加载器也无法重新加载该类。
