前言
呵呵, 最近看到了这样的一篇文档, Java类初始化的相互引用和次序问题 , 也是一个很经常看到的基础问题
但是 在以前的话, 我一定是思考那些理论来分析这个问题, 比如 main 中 读取 Clazz1.x1 导致了 Clazz1 的初始化, Clazz1 初始化的时候, 会使用到 Clazz2, 然后 Clazz2 初始化的时候回再次 触发 Clazz1 的初始化
然后 这里出现了 相互依赖, 根据规范 5.5. Initialization, "If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally."
因此触发 Clazz1 的初始化正常完成了, 然后继续往下走
那么 我们便来调试一下这个整个过程, 因为理论上的知道, 和实际调试起来 完全是两回事情, 也许会让你有一些 额外的收货
一下代码, 截图 基于 jdk9
首先是测试用例
package com.hx.test04;
/**
* LoadRefEachOther
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-03-08 10:58
*/
public class Test06LoanRefEachOther {
// Test06LoanRefEachOther
// refer : https://hllvm-group.iteye.com/group/topic/38847
public static void main(String[] args) {
System.out.println(Clazz1.x1);
// System.out.println(Clazz2.x1);
System.out.println(Clazz2.x2);
}
/**
* Clazz1
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-03-08 10:59
*/
private static class Clazz1 {
static int x1 = 1;
int f01;
int f02;
int f03;
int f04;
int f05;
static {
x1 = Clazz2.x2;
}
}
/**
* Clazz2
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2020-03-08 11:00
*/
private static class Clazz2 extends Clazz1 {
static int x2 = 2;
int f01;
int f02;
int f03;
int f04;
int f05;
static {
x2 = Clazz1.x1;
}
}
}
以及运行结果
按照 上面的理论上的思考, 输出的结果, 也是这样
再上一部分下文需要参考的字节码
Test06LoanRefEachOther.main
Test06LoanRefEachOther
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field com/hx/test04/Test06LoanRefEachOther$Clazz1.x1:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #5 // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I
15: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
18: return
Test06LoanRefEachOther$Clazz1.<clinit>
Clazz1
static {};
Code:
0: iconst_1
1: putstatic #3 // Field x1:I
4: getstatic #4 // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I
7: putstatic #3 // Field x1:I
10: return
Test06LoanRefEachOther$Clazz2.<clinit>
Clazz2
static {};
Code:
0: iconst_2
1: putstatic #2 // Field x2:I
4: getstatic #3 // Field com/hx/test04/Test06LoanRefEachOther$Clazz1.x1:I
7: putstatic #2 // Field x2:I
10: return
准备的东西都这些了, 那么接下来 便是主题了
第一次初始化
可以看到, 第一次初始化的是 Clazz1, 并且是在 main 方法中初始化的
那么具体触发 Clazz1 初始化是哪一个字节码呢 ?
这个就需要我们查看下 当前运行时的情况了
我们打印一下 main方法 code 的相关信息
(lldb) print method->code_base()
(address) $240 = 0x00000001042cbf48 "\xffffffb2\x01"
(lldb) print method->code_size()
(int) $241 = 19
(lldb) x 0x00000001042cbf48
0x1042cbf48: b2 01 00 b2 02 00 b6 03 00 b2 01 00 b2 04 00 b6 ................
0x1042cbf58: 03 00 b1 ff 00 1e 4a 4a 00 00 00 00 13 00 17 00 ......JJ........
# code 拆解如下
0x1042cbf48: b2 01 00: getstatic #1 // Field java/lang/System.out:Ljava/io/PrintStream;
0x1042cbf4b: b2 02 00: getstatic #2 // Field com/hx/test04/Test06LoanRefEachOther$Clazz1.x1:I
0x1042cbf4e: b6 03 00: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
0x1042cbf51: b2 01 00: getstatic #1 // Field java/lang/System.out:Ljava/io/PrintStream;
0x1042cbf54: b2 04 00: getstatic #4 // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I
0x1042cbf57: b6 03 00: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
0x1042cbf5a: b1: return
再查看一下 r13 寄存器, 指向的是 0x00000001042cbf4b
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b2
rbp = 0x00007000100d0698
rsp = 0x00007000100d0640
r12 = 0x0000000000000000
r13 = 0x00000001042cbf4b
r14 = 0x00007000100d06a8
r15 = 0x00007fb91180f800
rip = 0x0000000108071120
13 registers were unavailable.
对应的是 "getstatic #2 // Field com/hx/test04/Test06LoanRefEachOther$Clazz1.x1:I"
因此 第一次加载是在 main 里面的 "System.out.println(Clazz1.x1);" 触发加载的
第二次初始化
第二次的初始化, 同样初始化的是 Clazz1, 是在 Clazz1 的类初始化方法里面
我们打印一下 Clazz1.<clinit>方法 code 的相关信息
(lldb) print method->code_base()
(address) $247 = 0x00000001042cc480 "\x04\xffffffb3\x02"
(lldb) print method->code_size()
(int) $248 = 11
(lldb) x 0x00000001042cc480
0x1042cc480: 04 b3 02 00 b2 03 00 b3 02 00 b1 ff 00 3c ff 08 .............<..
0x1042cc490: 10 31 00 00 00 00 00 00 40 6f 43 03 01 00 00 00 .1......@oC.....
# code 拆解如下
0x1042cc480: 04: iconst_1
0x1042cc481: b3 02 00: putstatic #2 // Field x1:I
0x1042cc484: b2 03 00: getstatic #3 // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I
0x1042cc487: b3 02 00: putstatic #2 // Field x1:I
0x1042cc48a: b1: return
我们再看一下 r13 寄存器
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b3
rbp = 0x00007000100cf600
rsp = 0x00007000100cf5a0
r12 = 0x0000000000000000
r13 = 0x00000001042cc481
r14 = 0x00007000100cf608
r15 = 0x00007fb91180f800
rip = 0x0000000108071632
13 registers were unavailable.
对应的是 "putstatic #2 // Field x1:I", 并且这里的初始化 是由于 main 方法中 Clazz1 的初始化, 进而调用 Clazz1.<clinit>, 进而再一次 尝试初始化 Clazz1, 这一次的调用出口是
第三次初始化
这次初始化的是 Clazz2, 然后 触发的地方是 Clazz1.<clinit> 里面
呵呵 相信这一次 具体的调用的地方在哪里, 大家也能猜出来了吧
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b2
rbp = 0x00007000100cf600
rsp = 0x00007000100cf5b0
r12 = 0x0000000000000000
r13 = 0x00000001042cc484
r14 = 0x00007000100cf608
r15 = 0x00007fb91180f800
rip = 0x0000000108071120
13 registers were unavailable.
第四次初始化
这次是初始化 Clazz1, 触发的地方是在 Clazz2 的初始化需要初始化 其父类 Clazz1
这一次的调用出口是 递归初始化 complete normally.
第五次初始化
这一次初始化的是 Clazz2, 触发的地方是在 Clazz2.<clinit> 方法里面
我们打印一下 Clazz2.<clinit>方法 code 的相关信息
(lldb) print method->code_base()
(address) $279 = 0x00000001042cc898 "\x05\xffffffb3\x01"
(lldb) print method->code_size()
(int) $280 = 11
(lldb) x 0x00000001042cc898
0x1042cc898: 05 b3 01 00 b2 02 00 b3 01 00 b1 ff 00 68 ff 08 .............h..
0x1042cc8a8: 10 31 00 00 00 00 00 00 40 6f 43 03 01 00 00 00 .1......@oC.....
# code 拆解如下
0x1042cc898: 05: iconst_2
0x1042cc899: b3 01 00: putstatic #1 // Field x2:I
0x1042cc89c: b2 02 00: getstatic #2 // Field com/hx/test04/Test06LoanRefEachOther$Clazz1.x1:I
0x1042cc89f: b3 01 00: putstatic #1 // Field x2:I
0x1042cc8a2: b1: return
我们再看一下 r13 寄存器
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b3
rbp = 0x00007000100ce570
rsp = 0x00007000100ce510
r12 = 0x0000000000000000
r13 = 0x00000001042cc899
r14 = 0x00007000100ce578
r15 = 0x00007fb91180f800
rip = 0x0000000108071632
13 registers were unavailable.
这次加载处理的时候 对应的字节码是 "putstatic #2 // Field x2:I"
这一次的调用出口是 递归初始化 complete normally.
第六次初始化
这一次加载的是 Clazz1, 触发的地方是在 Clazz2.<clinit> 里面
查看一下 r13 寄存器, 具体的字节码是在
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b2
rbp = 0x00007000100ce570
rsp = 0x00007000100ce520
r12 = 0x0000000000000000
r13 = 0x00000001042cc89c
r14 = 0x00007000100ce578
r15 = 0x00007fb91180f800
rip = 0x0000000108071120
13 registers were unavailable.
这一次的调用出口是 递归初始化 complete normally.
第七次初始化
这一次加载的是 Clazz2, 触发的地方是在 Clazz2.<clinit> 里面
查看一下 r13 寄存器, 具体的字节码是在
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b3
rbp = 0x00007000100ce570
rsp = 0x00007000100ce510
r12 = 0x0000000000000000
r13 = 0x00000001042cc89f
r14 = 0x00007000100ce578
r15 = 0x00007fb91180f800
rip = 0x0000000108071632
13 registers were unavailable.
这一次的调用出口是 递归初始化 complete normally.
这里执行的便是 Clazz2.<clinit> 里面倒数第二个语句, putstatic
当吧 Clazz2.<clinit> 执行完成, 再把后面的 initialize_impl 的相关业务处理完成, Clazz2 的初始化也就完成了
第八次初始化
这一次加载的是 Clazz1, 触发的地方是在 Clazz1.<clinit> 里面
到这里就说明 Clazz2 初始化完成了, 目前是到了 "x1 = Clazz2.x2;" 的赋值处理这里了
查看一下 r13 寄存器, 具体的字节码是在
(lldb) re r
General Purpose Registers:
rbx = 0x00000000000000b3
rbp = 0x00007000100cf600
rsp = 0x00007000100cf5a0
r12 = 0x0000000000000000
r13 = 0x00000001042cc487
r14 = 0x00007000100cf608
r15 = 0x00007fb91180f800
rip = 0x0000000108071632
13 registers were unavailable.
这里执行的便是 Clazz1.<clinit> 里面倒数第二个语句, putstatic
当把Clazz1.<clinit> 执行完成, 再把后面的 initialize_impl 的相关业务处理完成, Clazz1 的初始化也就完成了
呵呵, 不知道这和各位开始看到这个问题的时候 思考的是否一样呢 ?
细心的你, 可能会发现 code 运行时的字节码和 class 文件中的字节码有一些不一样的地方 ?
发现没得, getstatic, putstatic, invokevirtual 的操作数运行时 和 class文件中 是不一样的, 这是咋回事呢, 这个是这个index是在Rewriter阶段重写过的(这里的细节与本文没有关系, 暂时不展开)