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

本问题参考自: www.zhihu.com/question/43… 解答为个人原创

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,这时候才需要锁。这样大大减少了锁使用。

image

更详细的 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 属性辅助我们真正创建对象,避免编译器优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini复制代码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,输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
ini复制代码[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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scss复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ini复制代码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;
}

每日一刷,轻松提升技术,斩获各种offer:

image

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%