【JAVA基础面经】Java对象创建与类加载过程
·
创建对象的过程
一个简单的 new 关键字背后,JVM 实际上执行了一系列复杂的步骤,图片来源

- 类加载检查:检查常量池中是否存在该类的符号引用,并判断类是否已加载、解析、初始化。如果没有加载则触发类加载过程。
- 为对象分配内存:在堆中划分一块确定大小的内存空间
- 初始化零值:将分配的内存空间初始化为零值(不包括对象头) 这保证了对象的实例字段在不赋初值的情况下也能直接使用
- 设置对象头:设置这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息
- 执行 init 方法:调用类的构造函数 <init>,完成实例变量的真正赋值和初始化块执行
类加载的过程
Java 类的生命周期从字节码文件到能被 JVM 使用,需要经历 加载(Loading)→ 验证(Verification)→ 准备(Preparation)→ 解析(Resolution)→ 初始化(Initialization)→ 使用(Using)→ 卸载(Unloading) 七个阶段。其中,前五个阶段统称为 类加载过程。
图片来源
- 加载(将字节码读入内存):这是类加载的第一步,由具体的类加载器完成。首先通过类的全限定名,从文件系统、网络、JAR 包、动态生成等方式获取 .class 文件的二进制数据。随后将字节流的静态存储结构转化为方法区的运行时数据结构。最后在 Java 堆中创建一个 java.lang.Class 对象,作为访问该类在方法区数据的入口。
- 连接(验证、准备、解析 3 个阶段统称为连接)
- 验证(确保字节码安全合法):确保加载的字节流符合 JVM 规范,不会危害虚拟机自身安全。包括文件格式验证、元数据验证、字节码验证、符号引用验证。
- 准备(为静态变量分配内存并赋零值):在方法区中为类变量(static 变量)分配内存,并设置系统初始零值。对于 static final 修饰的常量(基本类型或字符串字面量),在编译期已确定值,准备阶段会直接赋值为代码中定义的值,而非零值。
- 解析(将符号引用替换为直接引用):将常量池中的符号引用(如 #2 指向类名 java/lang/String)替换为直接引用(指向内存中实际地址的指针、句柄或偏移量)
- 初始化(真正执行 Java 代码):这是类加载过程的最后一步,也是真正开始执行类中定义的 Java 代码的阶段。初始化阶段会执行静态变量显式赋值、执行静态代码块。
- 使用:使用类或者创建对象
- 卸载:一个类要被JVM卸载,需要同时满足该类所有的实例都已经被回收、加载该类的ClassLoader已经被回收、类对应的Java.lang.Class对象没有任何地方被引用。
类加载器
Java 的类加载器主要分为三种(JDK 8 及之前),它们遵循双亲委派模型,呈父子层级关系。JDK 9 之后引入模块化,类加载器体系略有调整。
- 启动类加载器(Bootstrap ClassLoader):由 C++ 实现,是 JVM 的一部分,在 Java 代码中无法直接获取其引用。负责加载 <JAVA_HOME>/lib 目录下的核心类库,如 rt.jar、resources.jar、charsets.jar 等。其作用是加载 Java 语言的核心基础类,例如 java.lang.*、java.util.*、java.io.* 等。这是 JVM 运行的根本保障。
- 扩展类加载器(Extension ClassLoader / Platform ClassLoader):其作用是加载 Java 平台的扩展功能类库,在 JDK 9 后主要为模块化系统的平台模块服务。
- JDK 8 及之前:名为 扩展类加载器 Extension ClassLoader,负责加载 <JAVA_HOME>/lib/ext 目录下的扩展类库,或者由系统变量 java.ext.dirs 指定的路径。
- JDK 9 之后:由于模块化系统的引入,扩展机制被移除,改为 平台类加载器(Platform ClassLoader)。它负责加载平台模块中的类,部分原属于扩展类加载器和启动类加载器的类(如 java.sql、java.xml)移交给了平台类加载器。
- 应用程序类加载器(Application ClassLoader / System ClassLoader):通常调用 ClassLoader.getSystemClassLoader() 获得,负责加载用户类路径(Classpath)下指定的类库或 .class 文件。其作用是加载开发者自己编写的应用程序代码和第三方依赖 JAR 包。这是程序默认使用的类加载器,如果没有特殊指定,用户代码均由它加载。
- 自定义类加载器(Custom ClassLoader):开发者通过继承 java.lang.ClassLoader 并重写 findClass(String name) 方法来实现。用于满足特殊场景的类加载需求,例如:加载非标准来源的字节码(如从网络下载、从数据库读取、从加密包解密);实现类隔离(如 Tomcat 为不同 Web 应用使用独立的 WebappClassLoader,防止不同应用的类库冲突);实现热部署(动态加载新的类文件替换旧类)。
这些类加载器之间的关系形成了双亲委派模型,其核心思想是当一个类加载器收到类加载的请求时,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。
双亲委派模型
当一个类加载器收到类加载请求时,它不会自己先加载,而是将请求委派给父加载器。只有当父加载器无法完成加载时,子加载器才会尝试自己加载。
// ClassLoader.loadClass 源码简化逻辑
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查该类是否已被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 有父加载器,则委派给父加载器
c = parent.loadClass(name, false);
} else {
// 3. 没有父加载器(已是顶层),交给 Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法加载,忽略异常,继续向下
}
if (c == null) {
// 4. 父加载器加载失败,自己尝试加载
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
}
双亲委派模型的作用
- 避免类的重复加载:父加载器已经加载过的类,子加载器无需重复加载,保证 JVM 中每个类的唯一性。
- 保护核心 API 安全:用户无法通过自定义 java.lang.String 类来替换 JDK 核心类。即使你写了一个同名类,Bootstrap 会优先加载官方的,恶意代码无法篡改核心库。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)