类的加载和对象的创建
类的加载和对象的创建
类的加载过程(JVM层面)
当首次使用一个类时(如创建对象、访问静态成员等),JVM会按以下步骤加载并初始化类:
1.加载(Loading)
- 通过类加载器(ClassLoader)查找类的字节码文件(.class)。
- 将字节码转换为方法区中的运行时数据结构(JVM内部表示)。
- 在堆中生成一个代表该类的 Class 对象(如 java.lang.Class 实例)。
2.链接(Linking) - 验证:检查字节码的安全性、结构正确性(如是否篡改)。
- 准备:为静态变量分配内存并赋默认值(如 int 默认为0,对象引用默认为null)。
- 解析:将符号引用(如方法名)替换为直接内存地址(可选,可延迟到初始化后)。
3.初始化(Initialization) - 执行类构造器 () 方法(由编译器自动生成):
- 按顺序执行静态变量的显式赋值(如 static int a = 5;)。
- 执行静态代码块(static { … })。
- 父类优先:若父类未初始化,先初始化父类。
关键点: - 类加载是按需进行(如第一次使用 new、访问静态成员等触发)。
- final static 常量(编译期可确定值)会在编译时直接替换,不触发初始化。
二、对象的创建过程(new 关键字触发)
类加载:首次遇到类时,JVM通过类加载器定位并加载.class文件。
静态初始化:执行静态变量赋值和静态代码块(仅首次加载时一次)。
内存分配:new指令触发堆内存分配,空间大小由类结构决定。
默认初始化:所有实例变量赋默认值(数值=0,boolean=false,引用=null)。
显式初始化:按代码声明顺序执行实例变量赋值和实例代码块。
构造器执行:调用对应构造函数(若无继承,直接执行;有继承则先递归调用父类构造器)。
详细:当类已加载完成后,通过 new 创建对象:
1.检查类是否已加载
- 若类未加载,先触发类加载过程(见上文),否则直接进入对象创建。
2.分配内存 - 在堆中为对象分配连续内存空间(大小在类加载时已确定)。
- 分配方式:指针碰撞(内存规整)或空闲列表(内存不规整)。
3.内存空间零值初始化 - 将分配的内存空间中的所有字段置零值(如 int=0, boolean=false, 引用=null)。
4.设置对象头(Object Header) - 存储对象元数据:类指针(指向方法区的类信息)、哈希码、GC分代年龄等。
5.执行 () 方法(构造函数) - 按顺序执行:
i.调用父类构造函数(隐式或显式 super())。
ii.执行实例变量显式赋值(如 int age = 18;)。
iii.执行构造代码块({ … })。
iv.执行自定义构造函数逻辑(用户编写的代码)。
三、代码示例与执行顺序
java运行复制class Parent {
static { System.out.println(“Parent静态代码块”); }
{ System.out.println(“Parent构造代码块”); }
Parent() { System.out.println(“Parent构造函数”); }
}
class Child extends Parent {
static int num = 10; // 静态变量
static { System.out.println(“Child静态代码块”); }
int age = initAge(); // 实例变量
{ System.out.println("Child构造代码块"); }Child() { System.out.println("Child构造函数");
}int initAge() {System.out.println("初始化实例变量");return 18;
}public static void main(String[] args) {System.out.println("首次创建对象 -->");new Child(); // 触发类加载+对象创建System.out.println("\n再次创建对象 -->");new Child(); // 仅触发对象创建
}
}
输出结果:
plaintext复制首次创建对象 -->
Parent静态代码块 // 父类初始化(静态)
Child静态代码块 // 子类初始化(静态)
Parent构造代码块 // 父类实例化(代码块)
Parent构造函数 // 父类构造函数
初始化实例变量 // 子类实例变量赋值
Child构造代码块 // 子类实例化(代码块)
Child构造函数 // 子类构造函数
再次创建对象 --> // 类已加载,跳过静态阶段
Parent构造代码块
Parent构造函数
初始化实例变量
Child构造代码块
Child构造函数
四、关键区别总结
五、特殊情况
1.不触发类初始化的操作
- 访问 final static 常量(编译期优化)。
- 通过数组声明(如 MyClass[] arr = new MyClass[10])。
- 调用 ClassLoader.loadClass()(仅加载,不初始化)。
2.对象创建的额外步骤 - 若开启逃逸分析,JVM可能优化为栈上分配或标量替换。
- 对象内存布局可能包含对齐填充(保证地址对齐)。