Java对象生命周期全解析:从类加载到GC回收的完整旅程
一、对象诞生阶段(加载与初始化)
Ⅰ 类加载机制
当程序首次访问某个类时,JVM通过多级加载器完成类信息的加载与验证:
public class Person {
// 类首次使用时触发加载流程
static {
System.out.println("Person类初始化完成");
// 类初始化阶段执行静态代码块:ml-citation{ref="7" data="citationList"}
}
}
加载过程:
- 加载(Loading) :通过
BootStrapClassLoader→ExtClassLoader→AppClassLoader三级委派机制加载.class文件47 - 验证(Verification) :检查字节码格式防止恶意代码注入(如修改魔数0xCAFEBABE)
- 准备(Preparation) :为静态变量分配内存并赋默认值(如int→0,对象→null)17
- 解析(Resolution) :常量池符号引用转为直接引用
- 初始化(Initialization) :执行
<clinit>方法完成静态变量赋值7
Ⅱ 对象实例化
Person p = new Person();
// 真实内存操作步骤:
// 1. 指针碰撞或空闲列表方式堆内存分配(堆内存连续时优先指针碰撞):ml-citation{ref="5,8" data="citationList"}
// 2. 内存空间初始化为零值(int age→0,String name→null)
// 3. 设置对象头(MarkWord+类元指针)
// 4. 执行构造函数<init>进行赋值:ml-citation{ref="7" data="citationList"}
内存分配策略:
- 新对象优先进入Eden区:默认占堆内存1/3空间18
- TLAB优化:通过
-XX:+UseTLAB开启线程私有分配缓冲区,减少内存竞争7
二、对象存活阶段(堆内存迁移)
Ⅰ 新生代存活周期
public class SurvivorDemo {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
for(int i=0; i<100; i++) {
// 每次分配1MB内存触发Minor GC
byte[] data = new byte[1024*1024];
list.add(data);
// 第3次循环时Survivor区满,触发晋升
if(i == 2) list.clear();
}
}
}
迁移过程:
-
Eden区分配:新对象99%在此分配(例外:大对象直接进老年代)
-
Minor GC触发:Eden满时启动复制算法,存活对象转移到Survivor区
-
年龄计数器:每次GC后对象年龄+1(最大值15,可通过
-XX:MaxTenuringThreshold调整)15 -
晋升条件:
- 年龄超过阈值
- Survivor区同年龄对象总大小超过其空间50%
- 分配担保失败(老年代剩余空间不足新生代对象总大小)58
Ⅱ 老年代生命周期
// 长期存活对象示例:缓存系统
public class CacheSystem {
private static Map<String,Object> cache = new HashMap<>();
public static void cacheData(String key, Object value) {
cache.put(key, value); // 缓存长期持有引用
}
public static void clearCache(String key) {
cache.remove(key); // 显式移除引用才能被回收
}
}
老年代特点:
-
存放长期存活对象(如Spring单例Bean)
-
采用标记-清除或标记-整理算法(CMS/G1可并发处理)56
-
Full GC触发条件:
- 老年代空间不足
- 永久代/元空间不足(JDK8+)
- System.gc()显式调用(不推荐)8
三、对象终结阶段(GC回收)
Ⅰ 回收判定算法
// 可达性分析示例
public class GCRootsDemo {
Object instance;
public static void main(String[] args) {
GCRootsDemo a = new GCRootsDemo();
GCRootsDemo b = new GCRootsDemo();
a.instance = b; // 循环引用
b.instance = a;
a = null; // 切断GC Roots
b = null;
System.gc(); // 循环引用仍会被回收
}
}
回收判定机制:
-
可达性分析:从GC Roots(栈引用、静态变量等)出发遍历引用链
-
两次标记流程:
- 第一次标记不可达对象
- 执行finalize()方法后二次标记(对象复活唯一机会)6
Ⅱ 回收执行过程
public class FinalizeDemo {
static FinalizeDemo hook;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("执行finalize方法");
hook = this; // 对象复活
}
public static void main(String[] args) throws Exception {
hook = new FinalizeDemo();
hook = null;
System.gc();
Thread.sleep(500);
System.out.println(hook != null ? "对象存活" : "对象被回收");
}
}
终结阶段流程:
- 不可见阶段:超出作用域但仍有潜在引用(如线程局部变量)
- 不可达阶段:无任何GC Roots可达
- 收集阶段:被GC标记为待回收对象
- 终结阶段:执行finalize()方法(不保证及时性)
- 内存释放:对象空间被重新分配给其他对象68
四、实战优化策略
Ⅰ 内存泄漏检测
// 典型内存泄漏场景:未关闭的资源
public class ResourceLeak {
public static void main(String[] args) {
List<Connection> connections = new ArrayList<>();
while(true) {
Connection conn = DriverManager.getConnection(DB_URL);
connections.add(conn); // 未关闭的连接持续堆积
// 正确做法:使用try-with-resources自动关闭
}
}
}
检测工具:
- jmap:生成堆转储文件
- VisualVM:实时监控堆内存变化
- Eclipse MAT:分析内存泄漏根源46
Ⅱ GC调优参数
| 参数 | 作用描述 | 适用场景 |
|---|---|---|
| -Xms2048m | 初始堆大小 | 避免堆动态调整开销 |
| -Xmx2048m | 最大堆大小 | 防止OOM |
| -XX:+UseG1GC | 启用G1收集器 | 大内存低延迟场景 |
| -XX:MaxGCPauseMillis=200 | 最大GC停顿时间目标 | 响应敏感型系统 |
| -XX:SurvivorRatio=8 | Eden与Survivor区占比 | 调整新生代对象存活时间 |