每日一面 - java中,描述一下什么情况下,对象会从年轻代进入老年代?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 每日一面 - java中,描述一下什么情况下,对象会从年轻代进入老年代?
本问题参考zi: https://www.zhihu.com/question/437632685, 解答为 个人原创


Key Takeaway


  • Java 默认启用了分代 GC
  • 启用分代 GC 的,在发生 Young GC,更准确地说是在 Survivor 区复制的时候,存活的对象的分代年龄会加1
  • 分代年龄 = -XX:MaxTenuringThreshold 指定的大小时,对象进入老年代
  • 还有动态晋升到老年代的机制,首先根据 -XX:TargetSurvivorRatio (默认 50,也就是 50%) 指定的比例,乘以 survivor 一个区的大小,得出目标晋升空间大小。然后将分代对象大小,按照分代年龄从小到大相加,直到大于目标晋升空间大小。之后,将得出的这个分代年龄以上的对象全部晋升
  • 对于一些的 GC 算法,还可能直接在老年代上面分配,例如 G1 GC 中的 humongous allocations(大对象分配),就是对象在超过 Region 一半大小的时候,直接在老年代的连续空间分配。


对象分配


我们一般认为 Java 中 new 的对象都是在堆上分配,这个说法不够准确,应该是大部分对象在堆上的TLAB分配,还有一部分在栈上分配或者是堆上直接分配,可能 Eden 区也可能年老代。同时,对于一些的 GC 算法,还可能直接在老年代上面分配,例如 G1 GC 中的humongous allocations(大对象分配),就是对象在超过 Region 一半大小的时候,直接在老年代的连续空间分配。


这里,我们先只关心 TLAB 分配。 对于单线程应用,每次分配内存,会记录上次分配对象内存地址末尾的指针,之后分配对象会从这个指针开始检索分配。这个机制叫做 bump-the-pointer (撞针)。 对于多线程应用来说,内存分配需要考虑线程安全。最直接的想法就是通过全局锁,但是这个性能会很差。为了优化这个性能,我们考虑可以每个线程分配一个线程本地私有的内存池,然后采用 bump-the-pointer 机制进行内存分配。这个线程本地私有的内存池,就是 TLAB。只有 TLAB 满了,再去申请内存的时候,需要扩充 TLAB 或者使用新的 TLAB,这时候才需要锁。这样大大减少了锁使用。


微信图片_20220624200148.jpg


更详细的 TLAB 理解,请参考: 通过 JFR 与日志深入探索 JVM - TLAB 原理详解


分代年龄


分代年龄位于对象头中,用于分代 GC.记录分代年龄一共 4 bit,所以最大为 2^4 - 1 = 15。所以配置最大分代年龄-XX:MaxTenuringThreshold=n这个n不能大于16,当然也不能小于 0.等于 0 的话,就直接入老年代。等于 16 的话(但是不能设置为 16 哟),就是从不进入老年代。默认是 15。


在发生 Young GC,更准确地说是在 Survivor 区复制的时候,存活的对象的分代年龄会加1。我们编写程序测试下,由于 编译器会优化代码,同时调用System.gc()并不是立刻触发 GC,并且是 Full GC,可能会使对象直接进入老年代,分代年龄不再增长,所以我们可以使用 volatile 属性辅助我们真正创建对象,避免编译器优化:

static volatile Object consumer;
public static void main(String[] args) throws Exception {
    //这是我们要观察的对象
    Object instance = new Object();
    long lastAddr = VM.current().addressOf(instance);
    for (int i = 0; i < 10000; i++) {
        //查看地址是否发生了变化,代表是否发生了 Survivor 复制,或者是移动到老年代
        long currentAddr = VM.current().addressOf(instance);
        if (currentAddr != lastAddr) {
            //地址发生变化的时候,打印对象结构
            ClassLayout layout = ClassLayout.parseInstance(instance);
            System.out.println(layout.toPrintable());
            lastAddr = currentAddr;
        }
        for (int j = 0; j < 10000; j++) {
            //一直创建新对象
            //因为是volatile的属性更新,不会被编译器优化
            consumer = new Object();
        }
    }
}


可以配合 GC 日志一起观察,关于 JVM 日志配置可以参考这篇文章:OpenJDK 11 JVM日志相关参数解析与使用

首先我们用这个参数运行程序-Xmx128m -Xlog:gc=info,输出:

[0.016s][info][gc] Using G1
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
[2.540s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 24M->1M(128M) 2.600ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0d 00 00 00 (00001101 00000000 00000000 00000000) (13)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.627s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.273ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.675s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.063ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1d 00 00 00 (00011101 00000000 00000000 00000000) (29)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.724s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.068ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           25 00 00 00 (00100101 00000000 00000000 00000000) (37)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.772s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.212ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           2d 00 00 00 (00101101 00000000 00000000 00000000) (45)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.821s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.202ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           35 00 00 00 (00110101 00000000 00000000 00000000) (53)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.869s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.143ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           3d 00 00 00 (00111101 00000000 00000000 00000000) (61)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.917s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.313ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           45 00 00 00 (01000101 00000000 00000000 00000000) (69)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.969s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.473ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           4d 00 00 00 (01001101 00000000 00000000 00000000) (77)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.021s][info][gc] GC(9) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.283ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           55 00 00 00 (01010101 00000000 00000000 00000000) (85)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.072s][info][gc] GC(10) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.648ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           5d 00 00 00 (01011101 00000000 00000000 00000000) (93)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.122s][info][gc] GC(11) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.585ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           65 00 00 00 (01100101 00000000 00000000 00000000) (101)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.173s][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.130ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           6d 00 00 00 (01101101 00000000 00000000 00000000) (109)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.224s][info][gc] GC(13) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.078ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           75 00 00 00 (01110101 00000000 00000000 00000000) (117)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.273s][info][gc] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.135ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           7d 00 00 00 (01111101 00000000 00000000 00000000) (125)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.322s][info][gc] GC(15) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.467ms
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           7d 00 00 00 (01111101 00000000 00000000 00000000) (125)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.404s][info][gc] GC(16) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.556ms
[3.485s][info][gc] GC(17) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.303ms
[3.566s][info][gc] GC(18) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.288ms
[3.647s][info][gc] GC(19) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.317ms
[3.727s][info][gc] GC(20) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.286ms

可以看到,在第 15 次 GC 的时候,对象进入了老年代,内存地址不再随着 Young GC 的进行而变化。 更对对象头的详细信息,请参考:Java GC详解 - 1. 最全面的理解Java对象结构 - 对象指针 OOPs


动态晋升


动态晋升首先根据 TargetSurvivorRatio 指定的比例,乘以 survivor 一个区的大小,得出目标晋升空间大小。然后将分代对象大小,按照分代年龄从小到大相加,直到大于目标晋升空间大小。之后,将得出的这个分代年龄以上的对象全部晋升

动态修改 Tenuring Threshold,也就是晋升的分代年龄,源代码对应:src/hotspot/share/gc/serial/defNewGeneration.cpp

void DefNewGeneration::adjust_desired_tenuring_threshold() {
  // 获取 survivor 区大小,有两个 survivor 区,获取当前拷贝目标的那一个的大小
  size_t const survivor_capacity = to()->capacity() / HeapWordSize;
  // 计算 desired_survivor_size,通过 TargetSurvivorRatio
  size_t const desired_survivor_size = (size_t)((((double)survivor_capacity) * TargetSurvivorRatio) / 100);
  // 计算目标 Tenuring Threshold
  _tenuring_threshold = age_table()->compute_tenuring_threshold(desired_survivor_size);
  //后续都是数据采集统计以及日志,不用看
  if (UsePerfData) {
    GCPolicyCounters* gc_counters = GenCollectedHeap::heap()->counters();
    gc_counters->tenuring_threshold()->set_value(_tenuring_threshold);
    gc_counters->desired_survivor_size()->set_value(desired_survivor_size * oopSize);
  }
  age_table()->print_age_table(_tenuring_threshold);
}

计算目标 Tenuring Threshold 对应源码:src/hotspot/share/gc/shared/ageTable.cpp

uint AgeTable::compute_tenuring_threshold(size_t desired_survivor_size) {
  uint result;
  //如果是永远直接晋升或者从不晋升,则直接返回结果,不计算
  if (AlwaysTenure || NeverTenure) {
    assert(MaxTenuringThreshold == 0 || MaxTenuringThreshold == markWord::max_age + 1,
           "MaxTenuringThreshold should be 0 or markWord::max_age + 1, but is " UINTX_FORMAT, MaxTenuringThreshold);
    result = MaxTenuringThreshold;
  } else {
    size_t total = 0;
    uint age = 1;
    assert(sizes[0] == 0, "no objects with age zero should be recorded");
    //每个分代对象从分代年龄小加到大,直到大于 desired_survivor_size
    while (age < table_size) {
      total += sizes[age];
      // check if including objects of age 'age' made us pass the desired
      // size, if so 'age' is the new threshold
      if (total > desired_survivor_size) break;
      age++;
    }
    // 获取当前大于 desired_survivor_size 的分代年龄,将这个分代年龄以上的对象全部晋升到老年代
    result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
}



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
107 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2天前
|
Java
java代码优化:判断内聚到实体对象中和构造上下文对象传递参数
通过两个常见的java后端实例场景探讨代码优化,代码不是优化出来的,而是设计出来的,我们永远不可能有专门的时间去做代码优化,优化和设计在平时
22 15
|
1月前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
69 10
|
2月前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
3月前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
68 17
|
2月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
3月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
40 6
|
3月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
36 3
|
3月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
2月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
60 0