从 new 到 GC:一个Java对象的内存分配之旅

目录

摘要 :大家好,我是一个Java对象。你可能每天都会用new关键字创建成千上万个像我一样的兄弟姐妹,但你是否真正了解我们从诞生到消亡,在JVM内存中那段波澜壮阔的旅程呢?今天,就让我亲自为你揭秘,带你走一遍我们从分配到回收的完整路径。


一、我的诞生:一声new令下的抉择

我的故事始于一行你再熟悉不过的代码:

java 复制代码
MyObject obj = new MyObject();

当JVM执行到这行代码时,我的生命之旅便正式开启。但你可能不知道,我的第一个落脚点并非总是你想象中的那样。JVM会首先对我进行一个秘密的"背景审查"。

1. 栈上分配?一次惊险的"逃逸分析"

JVM会对我进行 逃逸分析(Escape Analysis)。它会判断我是否有可能"逃出"当前方法的范围,被其他方法或线程所引用。

  • 如果我没有"逃逸" :比如我只是一个局部变量,并且生命周期完全 confined 在这个方法内,不会被返回,也不会被赋值给外部变量。那么,恭喜你,也恭喜我!JVM会认为把我直接放在栈(Stack) 上是最高效的选择。
    • 为什么高效? 因为栈内存是线程私有的,分配速度极快,几乎没有延迟。更棒的是,当方法执行结束,栈帧弹出时,我所占用的内存会被自动、瞬间地清理掉,完全不需要惊动GC(垃圾回收器)这个大忙人。我的生命周期短暂而灿烂,随方法的结束而终结。
java 复制代码
// 这是一个我不会"逃逸"的例子
public void createUser() {
    User user = new User(); // 我,user对象,诞生了
    user.setName("local-user");
    // 我只在这个方法里被使用,没有被返回或传递出去
    System.out.println(user.getName());
} // 方法结束,栈帧弹出,我立刻就被销毁了,干净利落!

然而,在绝大多数复杂的业务场景中,我们对象总是承载着重要的数据,需要在方法之间传递,或者作为类的成员变量存在。这意味着,我"逃逸"了。于是,栈上分配的大门对我关闭,我将踏上前往堆(Heap) 的征途。

二、踏入堆内存:广阔天地,大有可为

堆内存是我们绝大多数对象真正的家。但这里等级森严,规矩繁多。我的第一站,取决于我的"体型"。

2. 大对象?直接晋升老年代!

JVM会掂量一下我的"体重"。如果我是一个非常大的对象(比如一个巨大的数组或长字符串),JVM会认为让我在年轻代(Young Generation)里反复"折腾"(复制)是一件非常不划算的事情。

  • 大对象的特殊通道 :为了避免在年轻代的Eden区和Survivor区之间进行高成本的内存复制,JVM为我这样的"大块头"开辟了一条绿色通道。我会被直接分配到老年代(Old Generation)
    • 这个"大"的标准由JVM参数 -XX:PretenureSizeThreshold 来定义。任何超过这个值的对象,都会享受这个特殊待遇。

风险提示 :虽然这看似是一种优待,但也是一把双刃剑。如果程序中频繁创建大对象,会迅速填满老年代,可能导致频繁的Full GC------这是一场波及整个堆内存的、更耗时的垃圾回收,会造成应用长时间的卡顿。所以,我的创造者们,请谨慎创建"庞然大物"哦!

3. TLAB?我的专属VIP分配区

如果我不是大对象,也无法在栈上分配,那么我将前往年轻代的Eden区。但在多线程环境下,Eden区就像一个繁忙的广场,所有的线程都想在这里抢占空间,这必然会导致线程安全问题和性能损耗。

为了解决这个问题,JVM为每个线程都提供了一个"小金库"------TLAB(Thread Local Allocation Buffer,线程本地分配缓冲区)

  • 我的VIP待遇 :当我被创建时,JVM会优先尝试在当前线程的TLAB中为我分配空间。
    • 好处:因为TLAB是线程私有的,所以在这里分配内存完全不需要加锁,速度飞快,极大地提升了并发环境下的对象分配效率。

只有当我的"体型"太大,TLAB放不下我,或者TLAB的剩余空间已经耗尽时,JVM才会尝试通过加锁(如CAS)的方式,直接在Eden区的公共空间为我分配内存。

三、在年轻代的颠沛流离:物竞天择,适者生存

现在,我终于在Eden区安顿下来了。这里充满了活力,到处都是和我一样新生的对象。然而,好景不长,Eden区的空间很快就会被填满。这时,我生命中的第一次大考------Minor GC------来临了。

4. Minor GC:第一次洗礼

当Eden区满时,JVM会触发Minor GC。这是一场只针对年轻代的垃圾回收。

  • 回收过程(基于复制算法)
    1. GC会扫描Eden区和其中一个Survivor区 (我们称之为From区),找出所有还"活着"(即被有效引用)的对象。
    2. 所有死去的对象都将被无情地抛弃,它们占用的空间会被一次性清空。
    3. 而我们这些幸存者,会被统一复制到另一个空的Survivor区 (我们称之为To区)。
    4. 完成复制后,我的年龄(Age) 会增加1。
5. 在Survivor区之间辗转

一次Minor GC后,我从Eden区搬到了Survivor区。但这并非安稳的开始,而是一段颠沛流离岁月的序幕。

  • 来回复制:年轻代有两个Survivor区,我们称之为S0和S1。它们在每次Minor GC后都会互换角色(From区变To区,To区变From区)。在接下来的每一次Minor GC中,我都会和来自Eden区以及另一个Survivor区的幸存者们一起,被复制到那个空的Survivor区,同时我的年龄计数器会继续加1。

这个过程就像一场残酷的生存游戏,每一轮都有大量的同伴离我而去。只有那些真正被长期需要的、生命力顽强的对象,才能一次又一次地存活下来。

四、终极归宿:晋升老年代

在Survivor区经历了数次洗礼后,我的年龄不断增长。终于,我迎来了"退休"的时刻。

6. 年龄达标,荣升老年代

JVM有一个晋升年龄的门槛,由参数 -XX:MaxTenuringThreshold 控制(默认通常是15)。

  • 晋升条件 :当我的年龄达到这个阈值时,在下一次Minor GC后,我将不再被复制到另一个Survivor区,而是会被晋升到老年代(Old Generation)

进入老年代,意味着我经过了重重考验,被认为是生命周期较长的对象。这里的空间更大,GC的频率也低得多。但这里的GC(Major GC / Full GC)一旦发生,代价也更为沉重。

注意:除了年龄达标,还有一些特殊情况也会让我提前晋升,比如:

  • 动态年龄判断:如果在Survivor区中,某一年龄及以下所有对象的总大小超过了Survivor区空间的一半,那么大于或等于这个年龄的对象将直接晋升老年代。
  • 空间分配担保:如果在Minor GC后,存活的对象无法被Survivor区容纳,这些对象也会被直接转移到老年代。

五、我的最终结局

我的生命最终会走向终点。可能是在某次Minor GC中被发现不再被需要,就此消散在年轻代;也可能是在老年代安度晚年后,在一次Full GC中被回收。当没有任何GCRoots引用链能够访问到我时,我就会被标记为垃圾,在下一次GC中被彻底清理,将内存归还给JVM,等待下一个"我"的诞生。

总结:我的生命周期图

回顾我的整个旅程,可以用下面这个流程来概括:

  1. 诞生 (new) -> 逃逸分析
  2. 栈上分配?
    • 是 -> -> 方法结束,生命终结
    • 否 ->
  3. 大对象?
    • 是 -> 老年代(Old)
    • 否 -> 年轻代
  4. TLAB分配?
    • 是 -> Eden (TLAB)
    • 否 -> Eden (公共区域)
  5. Eden区满 -> Minor GC -> 幸存者进入 Survivor区S0 (年龄+1)
  6. 再次Eden区满 -> Minor GC -> Eden和S0的幸存者进入 Survivor区S1 (年龄+1)
  7. 反复在S0和S1间流转,年龄不断增加...
  8. 年龄达到阈值?Survivor空间不足?
    • 是 -> 晋升老年代(Old)
    • 否 -> 继续在Survivor区流转
  9. 在某次 Minor GCFull GC 中被判定为垃圾 -> 生命终结

希望通过我的自述,你能更深刻地理解Java对象在JVM中这趟精妙而复杂的内存分配之旅。理解我们,才能更好地驾驭我们,写出更高效、更健壮的Java程序。

相关推荐
Sally璐璐2 小时前
Go组合式继承:灵活替代方案
开发语言·后端·golang
zzzsde2 小时前
【c++】类和对象(4)
开发语言·c++
Jooou2 小时前
并发:如何设计线程安全的类
java·并发
晨非辰2 小时前
#C语言——刷题攻略:牛客编程入门训练(十二):攻克 循环控制(四)、循环输出图形(一),轻松拿捏!
c语言·开发语言·经验分享·笔记·其他·学习方法·visual studio
gou123412342 小时前
Go语言io.Copy深度解析:高效数据复制的终极指南
开发语言·golang·php
考虑考虑3 小时前
图片翻转
java·后端·java ee
白玉cfc3 小时前
【OC】单例模式
开发语言·ios·单例模式·objective-c
十六点五3 小时前
Java NIO的底层原理
java·开发语言·python
猿究院-赵晨鹤3 小时前
Java I/O 模型:BIO、NIO 和 AIO
java·开发语言