Java
创建对象的时候,对象是在堆内存中创建的。但堆内存又分为新生代和老年代,新生代又细分为 Eden 空间、From Survivor 空间、To Survivor 空间。那创建的对象到底在哪里

一、对象优先在 Eden 分配

堆内存分为新生代和老年代,新生代是用于存放使用后准备被回收的对象,老年代是用于存放生命周期比较长的对象。
大部分创建的对象,都属于生命周期比较短的,所以会存放在新生代。新生代又细分 Eden 空间、From Survivor 空间、To Survivor 空间,创建的对象优先在 Eden 分配。
Java 创建的对象存放分析 - 图1
随着对象的创建,Eden 剩余内存空间越来越少,就会触发 Minor GC,于是 Eden 的存活对象会放入 From Survivor 空间。
Java 创建的对象存放分析 - 图2
Minor GC 后,新对象依然会往 Eden 分配。
Java 创建的对象存放分析 - 图3
Eden 剩余内存空间越来越少,又会触发 Minor GC,于是 Eden 和 From Survivor 的存活对象会放入 To Survivor 空间。
Java 创建的对象存放分析 - 图4

二、大对象直接进入老年代

在上面的流程中,如果一个对象很大,一直在 Survivor 空间复制来复制去,那很费性能,所以这些大对象直接进入老年代。
可以用 XX:PretenureSizeThreshold 来设置这些大对象的阈值。
Java 创建的对象存放分析 - 图5

三、长期存活的对象将进入老年代

在上面的流程中,如果一个对象 Hello_A,已经经历了 15 次 Minor GC 还存活在 Survivor 空间中,那他即将转移到老年代。这个 15 可以通过 -XX:MaxTenuringThreshold 来设置的,默认是 15。
虚拟机为了给对象计算他到底经历了几次 Minor GC,会给每个对象定义了一个对象年龄计数器。如果对象在 Eden 中经过第一次 Minor GC 后仍然存活,移动到 Survivor 空间年龄加 1,在 Survivor 区中每经历过 Minor GC 后仍然存活年龄再加 1。年龄到了 15,就到了老年代。
Java 创建的对象存放分析 - 图6

四、动态年龄判断

除了年龄达到 MaxTenuringThreshold 的值,还有另外一个方式进入老年代,那就是动态年龄判断:在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
比如 Survivor 是 100M,Hello1 和 Hello2 都是 3 岁,且总和超过了 50M,Hello3 是 4 岁,这个时候,这三个对象都将到老年代。
Java 创建的对象存放分析 - 图7

五、空间分配担保

上面的流程提过,存活的对象都会放入另外一个 Survivor 空间,如果这些存活的对象比 Survivor 空间还大呢?整个流程如下:

  • Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,则发起 Minor GC。
  • 如果小于,则看 HandlePromotionFailure 有没有设置,如果没有设置,就发起 full gc。
  • 如果设置了 HandlePromotionFailure,则看老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于,就发起 full gc。
  • 如果大于,发起 Minor GC。Minor GC 后,看 Survivor 空间是否足够存放存活对象,如果不够,就放入老年代,如果够放,就直接存放 Survivor 空间。如果老年代都不够放存活对象,担保失败(Handle Promotion Failure),发起 full gc。

Java 创建的对象存放分析 - 图8