大家好,我是大圣,很高兴又和大家见面。
今天给大家带来图解 JVM 系列的第五篇文章,主要说Java 对象的生命周期。
知识回顾 上一篇文章我们通过一个 Java 案例代码解释了平时我们写的 Java 代码是如何在 JVM 运行时数据区各个区域存储的,让大家直观感受到了 JVM 与 Java 代码的联系。这部分的内容希望大家可以认真去看看,这还是很重要的。
然后又说了 JVM 运行时数据区里面堆内存的详细区域,如下图:
今天这篇文章,我们说一个 Java 对象在 JVM 运行时数据区所经历的生命周期。
Java 对象的生命周期 通俗易懂的例子:一座城市的公共交通系统 新公交车的投入(对象的创建和分配在年轻代的 Eden 区) 新公交车被制造出来并投入到城市交通系统中,开始在短途线路上运行。这就像在 Java 中用 new 创建一个对象,它被分配到堆内存的年轻代的 Eden 区。
公交车的日常运行(第一次垃圾收集和移动) 当城市的公交车数量增多,需要调整路线以避免拥堵。这就像当 Eden 区域满了,触发第一次垃圾收集(Minor GC)。一些经常运行的公交车(存活的对象)被调整到其他线路(Survivor S0)。
公交车在不同线路间的转移(对象在Survivor之间的移动) 随着时间的推移,某些公交车可能会在不同的短途线路间来回调整,以满足不同的交通需求。这类似于对象在 Minor GC 后在两个Survivor(S0 和 S1)之间的移动。
公交车的长途运行(晋升到老年代) 一些公交车因为可靠和经常使用,被分配到长途或重要路线上,就像一些对象在年轻代中存活足够长的时间后被晋升到老年代。
公交车的退役和更新(老年代中的垃圾收集) 随着车辆老化或需求变化,城市交通部门会定期检查并淘汰那些不再适用的公交车,更新为更高效的新车。这就像老年代接近满时触发的 Major GC 或 Full GC,清理不再被需要的对象。
公交车的最终报废(对象的最终回收) 最终,那些老旧的公交车被彻底报废,它们的部件可能被回收再利用。在 Java 中,这相当于对象在老年代中不再被引用时的回收,它们占用的内存空间被释放,可供 JVM 重新利用。
对应 Java 对象的生命周期 通过这个例子,我们可以将公交车在城市交通系统中的各个阶段类比为 Java 对象在 JVM 内存中的生命周期。这个类比帮助我们理解对象是如何在内存中被创建、使用、移动和最终被回收的,反映了 Java 在内存管理上的自动化和智能化,以及不同类型垃圾收集器对性能的影响。
专业解释 对象的创建和分配(在年轻代的 Eden 区) 当使用 new 关键字创建 Java 对象时,JVM 首先在堆内存的年轻代的 Eden 区分配空间。
年轻代主要用于存储新生成的对象。大多数情况下,对象首先在 Eden 区被创建。
如下图:
第一次垃圾收集(Minor GC)和移动 当 Eden 区域满了,会触发第一次垃圾收集,即 Minor GC。
在这个过程中,仍然存活的对象被移动到一个Survivor(例如 S0)。这时,许多临时对象会在Eden 区域就会被清理掉,因为它们通常有短暂的生命周期。
如下图:
对象在Survivor之间的移动 随着更多的 Minor GC 发生,对象可能会在两个Survivor(S0 和 S1)之间来回移动。每次 Minor GC 后,存活的对象会从一个Survivor移动到另一个Survivor,并且每次移动都会增加对象的年龄。
如下图:
Survivor区的S0也可能需要进行垃圾回收,这可能导致内存空间出现不连续的情况。当Eden区再次分配对象后,也可能触发垃圾回收。
在这种情况下,Eden区和S0区的存活对象都会被复制到空闲的Survivor区的S1。这样做的目的是清空Eden区和S0区,将所有存活的对象集中到S1区。这样S0和S1 始终有一块空的区域,来解决空间不连续的问题。
晋升到老年代(Old Generation) 当一个对象在年轻代中存活足够长的时间(即达到一定的年龄阈值)后,它会被晋升到老年代。
老年代用于存储长期存活的对象。与年轻代相比,老年代的大小更大,且垃圾收集的频率更低。
老年代中的垃圾收集(Major GC / Full GC) 当老年代接近满时,会触发 Major GC 或 Full GC。这个过程通常比 Minor GC 更耗时,因为它涉及整个老年代区域的垃圾收集。
对象的最终回收 如果对象在老年代中仍然不再被引用,它最终将在 Major GC 或 Full GC 过程中被清理。
一旦对象被回收,它占用的内存空间就会被释放,可供 JVM 重新利用。
关键点 对象的生命周期:
对象从 Eden 区的创建开始,可能会经历多次 Minor GC,之后可能晋升到老年代,最终在不再被引用时被回收。
垃圾收集过程:
Minor GC 主要发生在年轻代,而 Major GC 或 Full GC 发生在老年代,两者的频率和影响范围不同。
对象晋升:
对象晋升的决定因素包括对象的年龄(在年轻代中经历的 GC 次数)以及老年代的空间情况。
代码案例
typescript
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void introduce() {
System.out.println("Hello, my name is " + name);
}
public static void main(String[] args) {
// 创建对象 (在 Eden 区)
Person person = new Person("Alice");
// 使用对象
person.introduce();
// 对象可能在这里进行第一次 Minor GC,如果 Eden 区满了
// 这时,person 可能会移到 S0 或 S1 区
// 对象不再被引用
person = null;
// 请求垃圾收集 (注意:这不能保证立即发生 GC)
System.gc();
// 代码执行完毕,退出 main 方法
}
}
大家可以看一下上面的代码,就是创建了一个 Person 类,然后在 main 方法里面进行初始化,接着调用。然后我们来讲解一下这个代码。
对象的创建(在 Eden 区) new Person("Alice") 在堆的 Eden 区创建了一个 Person 对象。
对象的使用 通过 person.introduce() 调用对象的方法。
可能的第一次 Minor GC 如果在创建对象后 Eden 区满了,可能会触发 Minor GC。存活的对象(如本例中的 person)可能会被移动到 S0 或 S1 区。
对象不再被引用 将 person 设置为 null,使得 Person 对象不再有任何强引用。
请求垃圾收集 System.gc() 请求 JVM 进行垃圾收集。但这个调用并不保证 JVM 会立即进行 GC。
对象的最终回收 如果垃圾收集器运行,它可能会发现 Person 对象不再被引用,并将其回收。
注意事项 垃圾收集的确切时机和对象在内存中的移动是由 JVM 控制的,可能因 JVM 的类型和配置而异。
总结 知识回顾 这篇文章主要讲了一个Java 对象从被我们创建到最后被回收的一整个过程,也让大家直观的感受到了JVM 管理 Java 对象的过程。
比如先刚创建出来的对象最开始大多数在在年轻代的 Eden 区,然后随着 Eden 区内存快满了,这个时候会触发Minor GC,然后 Eden 区会有一部分的对象会被回收掉,没有回收掉的对象就会移动到 Survivor 区域的 S0 区。
然后随着Survivor 区域的 S0 区的内存也快满的时候,这个时候S0 区也会进行内存回收,这个时候没有被回收的 Java 对象就会被移动的Survivor 区域的 S1 区,这样一直进行来回移动。
当对象被回收一定次数还是没被回收,这个时候就会移动到 Old 区域,随着 Old 区域内存快满了,就会触发 Full GC。
大家可能对什么对象会被回收、Minor GC、Full GC 这些名词有疑问,这个没有关系的,大家可以暂时理解哪些对象会被回收,其实就是不再被使用的对象;Minor GC、Full GC 其实就是进行对象的销毁,把占用的内存释放出来。
这些专业名词下篇文章都会详细说的,大家在这里有个直观的认识就行。
下期预告 下篇文章会说 JVM 内存区域还有一些知识没有讲完,比如 JVM 空间担保机制,还有一些大对象刚创建的时候就是在老年代的,接着会说 强引用&弱引用、栈帧、Java 对象模型、GC 类型、GCRoot、垃圾回收。给大家补这些 JVM 里面比较重要的概念。
本文由博客一文多发平台 OpenWrite 发布!