类加载检查阶段主要验证常量池中是否存在类的符号引用及该类是否已完成加载、解析和初始化,未通过则触发类加载;接口、抽象类或不存在类名会抛noclassdeffounderror或classnotfoundexception。

类加载检查阶段到底检查什么
JVM 在 new 指令执行时,会先检查 常量池 中是否已有该类的符号引用,且该类是否已被加载、解析和初始化。没通过就触发类加载——但注意:不是所有 new 都会触发完整加载流程,比如已加载过的类直接复用;而接口、抽象类、不存在的类名会抛出 NoClassDefFoundError 或 ClassNotFoundException。
- 类加载失败常见于:依赖 jar 包缺失、类路径污染、模块系统(Java 9+)中未声明
requires - 反射创建对象(
Class.forName().newInstance())也会走同样检查路径,但 JDK 9 起该方法已弃用,应改用Constructor.newInstance() - 如果类被定义为
final但构造器私有,检查能过,后续实例化会抛IllegalAccessException
内存分配时堆空间不够怎么办
对象内存分配发生在堆上,JVM 通常使用指针碰撞(Serial / Parallel GC)或空闲列表(CMS / ZGC)方式划分空间。分配失败不等于 OOM 立即发生——JVM 会先尝试触发一次 Minor GC,再重试分配。
- 若仍失败,才抛
OutOfMemoryError: Java heap space - 使用
-XX:+UseTLAB(默认开启)可减少线程间同步开销,但 TLAB 太小会导致频繁 refill,太大则浪费空间 - 大对象(如 >256KB,默认阈值,由
-XX:PretenureSizeThreshold控制)可能直接进老年代,跳过 Eden 区,影响 GC 策略判断
对象头初始化包含哪几块内容
对象头分两部分:Mark Word 和 Class Pointer(数组对象还有长度字段)。其中 Mark Word 在不同状态下含义不同:无锁时存哈希码、分代年龄、偏向锁标志位;加锁后存线程 ID、epoch、锁标志位等。
-
Class Pointer指向方法区中的类元数据(Klass),不是字符串类名,也不是 Class 对象本身 - 偏向锁启用时(
-XX:+UseBiasedLocking,JDK 15 起默认关闭),新对象默认进入“可偏向”状态,但首次加锁前 Mark Word 不记录任何线程信息 - 对象创建完毕后、构造器执行前,对象头已写入,此时若其他线程调用
Object.hashCode(),会强制设置哈希码并可能撤销偏向锁
构造器执行前对象就已经“存在”了
new 指令完成内存分配 + 对象头初始化后,对象在 JVM 内部就具备了身份(有地址、有类型、有锁信息),哪怕 <init></init> 方法还没开始执行。这意味着:
立即学习“Java免费学习笔记(深入)”;
- 其他线程可能通过逃逸分析外泄的引用看到一个“半初始化”的对象(尤其在构造器中启动线程或发布 this 引用)
-
final字段的语义保障仅在构造器正常结束时生效;若构造器抛异常,JVM 会清理对象内存,但引用若已泄露,外部可能看到字段为默认值(0 / null / false) - 使用
Unsafe.allocateInstance()可绕过构造器直接分配内存并初始化对象头,但字段全为默认值,且需手动调用ensureClassInitialized(),极易出错,生产环境禁止使用
对象头结构和锁状态切换是运行时动态决定的,不写死在 class 文件里;同一个对象在生命周期中,Mark Word 的布局可能变更多次——这点常被忽略,调试时看到对象哈希码突变或锁升级日志,根源往往在这里。









