<深入理解JVM字节码>读书笔记
Contents
class 文件结构
u1 表示 1 字节无符号
u2 表示 2 字节无符号
u4 表示 4 字节无符号
官方文档 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
magic number
查看
hexdump -C 文件
class 文件的 magic number 为 0xCAFEBABE
常量池
// 假设它的大小为 N, 则它的有效索引是 `[1, N-1]` . 注意, `long` 和 `double` 会占用两个索引的位置.
struct {
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count1];
}
cp_info {
u1 tag;
u1 info[];
}
tag
的类型有14 种
Constant Type | Value |
---|---|
CONSTANT_Class |
7 |
CONSTANT_Fieldref |
9 |
CONSTANT_Methodref |
10 |
CONSTANT_InterfaceMethodref |
11 |
CONSTANT_String |
8 |
CONSTANT_Integer |
3 |
CONSTANT_Float |
4 |
CONSTANT_Long |
5 |
CONSTANT_Double |
6 |
CONSTANT_NameAndType |
12 |
CONSTANT_Utf8 |
1 |
CONSTANT_MethodHandle |
15 |
CONSTANT_MethodType |
16 |
CONSTANT_InvokeDynamic |
18 |
各种类型的表示
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
// 它存储的是 Modified UTF8 而非 UTF8 字符串的内容
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
// 它存储的是常量池的索引
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
Access flags
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 |
Declared public ; may be accessed from outside its package. |
ACC_FINAL |
0x0010 |
Declared final ; no subclasses allowed. |
ACC_SUPER |
0x0020 |
Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE |
0x0200 |
Is an interface, not a class. |
ACC_ABSTRACT |
0x0400 |
Declared abstract ; must not be instantiated. |
ACC_SYNTHETIC |
0x1000 |
Declared synthetic; not present in the source code. |
ACC_ANNOTATION |
0x2000 |
Declared as an annotation type. |
ACC_ENUM |
0x4000 |
Declared as an enum type. |
this_class
, super_name
, interfaces
它们的值都是常量池中对应的索引值
fields
struct {
u2 fields_count;
field_info fields[fields_count];
}
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Access flag (访问标识)的值
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE |
0x0002 | Declared private ; usable only within the defining class. |
ACC_PROTECTED |
0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC |
0x0008 | Declared static . |
ACC_FINAL |
0x0010 | Declared final ; never directly assigned to after object construction. |
ACC_VOLATILE |
0x0040 | Declared volatile ; cannot be cached. |
ACC_TRANSIENT |
0x0080 | Declared transient ; not written or read by a persistent object manager. |
ACC_SYNTHETIC |
0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM |
0x4000 | Declared as an element of an enum . |
Descriptor (字段描述符) 的值
FieldType term | Type | Interpretation |
---|---|---|
B |
byte |
signed byte |
C |
char |
Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D |
double |
double-precision floating-point value |
F |
float |
single-precision floating-point value |
I |
int |
integer |
J |
long |
long integer |
L ClassName ; |
reference |
an instance of class ClassName |
S |
short |
signed short |
Z |
boolean |
true or false |
[ |
reference |
one array dimension |
methods
struct {
u2 methods_count;
method_info methods[methods_count];
}
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Access flags (访问标识)
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 |
Declared public ; may be accessed from outside its package. |
ACC_PRIVATE |
0x0002 |
Declared private ; accessible only within the defining class. |
ACC_PROTECTED |
0x0004 |
Declared protected ; may be accessed within subclasses. |
ACC_STATIC |
0x0008 |
Declared static . |
ACC_FINAL |
0x0010 |
Declared final ; must not be overridden. |
ACC_SYNCHRONIZED |
0x0020 |
Declared synchronized ; invocation is wrapped by a monitor use. |
ACC_BRIDGE |
0x0040 |
A bridge method, generated by the compiler. |
ACC_VARARGS |
0x0080 |
Declared with variable number of arguments. |
ACC_NATIVE |
0x0100 |
Declared native ; implemented in a language other than Java. |
ACC_ABSTRACT |
0x0400 |
Declared abstract ; no implementation is provided. |
ACC_STRICT |
0x0800 |
Declared strictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC |
0x1000 |
Declared synthetic; not present in the source code. |
attributes
struct {
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
class 文件中常见的属性
ConstantValue
: 用来表示常量值Code
: 它包含方法的字节码StackMapTable
Exceptions
BootstrapMethods
字节码
它使用的是 Big-endian
大端表示.
虚拟机栈和栈帧
- 基于栈: 移植性好, 指令更短, 实现简单, 但不能随机访问栈中的元素. 实现同样的功能, 需要的指令数比寄存器多. 要频繁地入栈出栈.
- Hotspot JVM
- 基于寄存器: 速度快, 充分利用寄存器. 但操作数需要显式指定, 指令较长.
- DalvikVM(android)
栈帧
- 局部变量表
- 操作数栈
指向常量池的引用
javac -g xxx.java javap -c -v -p -l xxx
指令
加载和存储指令
- load
- lload
- fload
- dload
- aload : 加载引用类型
- store
- lstore
- fstore
- dstore
- astore
- 常量加载
- const : 将常量值直接加载到操作数栈
- push : 将常量值直接加载到操作数栈
- ldc : 从常量池加载对应的常量到操作数栈
int 型常量根据不同的范围, 有不用的类型
[-1, 5]
: 使用iconst_n
的方式.-1
对应的是iconst_m1
[-128, 127]
: 使用bipush n
的方式[-32768, 32767]
: 使用sipush n
的方式- 其他范围, 则使用
ldc
的方式
操作数栈指令
- pop
- dup
- swap
运算和类型转换指令
运算(其他类推)
- iadd
- isub
- idiv
- imul
- irem
- ineg
- iand
- ior
- ixor
转换(其他类推)
- i2f
- i2b
控制转移指令
- ifle
- Ifge
等等
switch-case 底层实现
lookupswitch : 在 case 稀疏情况下使用
lookuptable : 在 case 紧凑的情况下使用
switch String 的字节码
public int switchString(final String name) {
switch (name) {
case "Java":
return 100;
case "Kolin":
return 101;
default:
return -1;
}
}
它的反编译代码为
public int switchString(String name) {
byte var3 = -1;
switch(name.hashCode()) {
case 2301506:
if (name.equals("Java")) {
var3 = 0;
}
break;
case 72678029:
if (name.equals("Kolin")) {
var3 = 1;
}
}
switch(var3) {
case 0:
return 100;
case 1:
return 101;
default:
return -1;
}
}
生成的字节码为
public int switchString(java.lang.String);
descriptor: (Ljava/lang/String;)I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=2
0: aload_1
1: astore_2
2: iconst_m1
3: istore_3
4: aload_2
5: invokevirtual #2 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 2
2301506: 36
72678029: 50
default: 61
}
36: aload_2
37: ldc #3 // String Java
39: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 61
45: iconst_0
46: istore_3
47: goto 61
50: aload_2
51: ldc #5 // String Kolin
53: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 61
59: iconst_1
60: istore_3
61: iload_3
62: lookupswitch { // 2
0: 88
1: 91
default: 94
}
88: bipush 100
90: ireturn
91: bipush 101
93: ireturn
94: iconst_m1
95: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 96 0 this Lio/emacsist/github/pdf/TestConst;
0 96 1 name Ljava/lang/String;
- 先调用 hashCode
- 根据 hashCode 跳转指定位置
- 再调用 equals 判断是否相等, 然后赋值到一个临时变量
- 再根据这个临时变量, 按普通的 switch 的 int 的形式调用
try finally 的实现
public class TestConst {
public void testFinally() {
try {
testThrow();
} catch (final Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
}
private void testThrow() {
final File f = new File("/tmp/hello.txt");
System.out.println(f.exists());
}
}
它生成的字节码
public void testFinally();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: invokespecial #2 // Method testThrow:()V
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #4 // String finally
9: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: goto 42
15: astore_1
16: aload_1
17: invokevirtual #7 // Method java/lang/Exception.printStackTrace:()V
20: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #4 // String finally
25: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: goto 42
31: astore_2
32: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
35: ldc #4 // String finally
37: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: aload_2
41: athrow
42: return
Exception table:
from to target type
0 4 15 Class java/lang/Exception
0 4 31 any
15 20 31 any
LineNumberTable:
line 8: 0
line 12: 4
line 13: 12
line 9: 15
line 10: 16
line 12: 20
line 13: 28
line 12: 31
line 13: 40
line 14: 42
LocalVariableTable:
Start Length Slot Name Signature
16 4 1 e Ljava/lang/Exception;
0 43 0 this Lio/emacsist/github/pdf/TestConst;
StackMapTable: number_of_entries = 3
frame_type = 79 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 79 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 10 /* same */
finally 的实现方式是在每个可能的路径上的最后的代码插入 finally 的代码, 这样子就可以保证 finally 的代码都会被执行(可以看到, 上面的 print finally 显示了 3 次).
这样子, 就可以理解, 如果在 try 及 finally 中都写了 return 的结果了.
如果在最后的 finally 赋值了, 比如下面
private static String testFinally() {
String s = "hello";
try {
return s;
} catch (final Exception e) {
return "excepto";
} finally {
s = "finally";
}
}
它的反编译代码为
private static String testFinally() {
String s = "hello";
String var2;
try {
String var1 = s;
return var1;
} catch (Exception var6) {
var2 = "excepto";
} finally {
s = "finally";
}
return var2;
}
try resource 实现
private static void tryResource() {
try (final FileWriter f = new FileWriter("/tmp/hello.txt")) {
f.write("hello");
f.flush();
} catch (final IOException e) {
e.printStackTrace();
}
}
它生成的字节码为
private static void tryResource();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC
Code:
stack=3, locals=5, args_size=0
0: new #2 // class java/io/FileWriter
3: dup
4: ldc #3 // String /tmp/hello.txt
6: invokespecial #4 // Method java/io/FileWriter."<init>":(Ljava/lang/String;)V
9: astore_0
10: aconst_null
11: astore_1
12: aload_0
13: ldc #5 // String hello
15: invokevirtual #6 // Method java/io/FileWriter.write:(Ljava/lang/String;)V
18: aload_0
19: invokevirtual #7 // Method java/io/FileWriter.flush:()V
22: aload_0
23: ifnull 91
26: aload_1
27: ifnull 46
30: aload_0
31: invokevirtual #8 // Method java/io/FileWriter.close:()V
34: goto 91
37: astore_2
38: aload_1
39: aload_2
40: invokevirtual #10 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
43: goto 91
46: aload_0
47: invokevirtual #8 // Method java/io/FileWriter.close:()V
50: goto 91
53: astore_2
54: aload_2
55: astore_1
56: aload_2
57: athrow
58: astore_3
59: aload_0
60: ifnull 89
63: aload_1
64: ifnull 85
67: aload_0
68: invokevirtual #8 // Method java/io/FileWriter.close:()V
71: goto 89
74: astore 4
76: aload_1
77: aload 4
79: invokevirtual #10 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
82: goto 89
85: aload_0
86: invokevirtual #8 // Method java/io/FileWriter.close:()V
89: aload_3
90: athrow
91: goto 99
94: astore_0
95: aload_0
96: invokevirtual #12 // Method java/io/IOException.printStackTrace:()V
99: return
Exception table:
from to target type
30 34 37 Class java/lang/Throwable
12 22 53 Class java/lang/Throwable
12 22 58 any
67 71 74 Class java/lang/Throwable
53 59 58 any
0 91 94 Class java/io/IOException
LineNumberTable:
line 9: 0
line 10: 12
line 11: 18
line 12: 22
line 9: 53
line 12: 58
line 14: 91
line 12: 94
line 13: 95
line 15: 99
LocalVariableTable:
Start Length Slot Name Signature
10 81 0 f Ljava/io/FileWriter;
95 4 0 e Ljava/io/IOException;
StackMapTable: number_of_entries = 10
frame_type = 255 /* full_frame */
offset_delta = 37
locals = [ class java/io/FileWriter, class java/lang/Throwable ]
stack = [ class java/lang/Throwable ]
frame_type = 8 /* same */
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 68 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 255 /* full_frame */
offset_delta = 15
locals = [ class java/io/FileWriter, class java/lang/Throwable, top, class java/lang/Throwable ]
stack = [ class java/lang/Throwable ]
frame_type = 10 /* same */
frame_type = 3 /* same */
frame_type = 255 /* full_frame */
offset_delta = 1
locals = []
stack = []
frame_type = 66 /* same_locals_1_stack_item */
stack = [ class java/io/IOException ]
frame_type = 4 /* same */
它的反编译代码为
private static void tryResource() {
try {
FileWriter f = new FileWriter("/tmp/hello.txt");
Throwable var1 = null;
try {
f.write("hello");
f.flush();
} catch (Throwable var11) {
var1 = var11;
throw var11;
} finally {
if (f != null) {
if (var1 != null) {
try {
f.close();
} catch (Throwable var10) {
var1.addSuppressed(var10);
}
} else {
f.close();
}
}
}
} catch (IOException var13) {
var13.printStackTrace();
}
}
对象相关的字节码指令
<init>
方法. 对象初始化方法, 类的构造方法, 非静态变量的初始化, 对象初始化代码块都会放在这里创建一个对象, 要用三条指令
public void testNew() { final Object o = new Object(); }
对应生成的字节码为
public void testNew();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lio/emacsist/github/pdf/TestConst;
8 1 1 o Ljava/lang/Object;
new
: 只是创建了一个类实例的引用, 然后压入操作数栈invokespecial
: 调用<init>
方法来调用构造函数初始化.dup
: 因为在调用invokespecial
后, 它会消耗操作数栈顶的实例引用. 如果不调用dup
后再调用invokespecial
的话, 则调用完后, 操作数栈顶就没有这个实例引用了. 因为<init>
方法并没有返回值<clinit>
: 静态初始化方法, 类静态初始化块, 静态变量初始化都会在这里. 在字节码里表示为static {}
public class TestConst { public static int a = 10; static { System.out.println("hello"); } public static int b = 20; }
它生成的字节码为
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: bipush 10
2: putstatic #2 // Field a:I
5: bipush 20
7: putstatic #3 // Field b:I
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #5 // String hello
15: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 4: 0
line 5: 5
line 9: 10
line 10: 18
字节码进阶
方法调用指令
invokestatic
: 调用静态方法- 不需要将对象加载到操作数栈
- 只需要将参数入栈, 然后就直接调用该指令
invokespecial
: 调用私有实例方法, 构造器方法以及 super 调用父类实例方法- 实例构造方法
<init>
- private 修饰的私有实例方法
- super 调用的父类方法
- 实例构造方法
invokevirtual
: 调用非私有实例方法- 需要将对象, 方法参数入栈
- 调用结束时, 对象引用, 方法参数都会出栈
- 如果有返回值, 则返回值入栈
invokeinterface
: 调用接口方法invokedynamic
: 调用动态方法. 调用流程:- 首次执行该指令时, 会调用 bootstrap method
- 上面的方法返回一个 CallSite 对象. CallSite 内部根据方法签名进行目标方法查找. 它的
getTarget
方法返回方法句柄 (MethodHandle
) 对象 - 在 CallSite 没变化的情况下, MethodHandle 可以一直调用. 如果有变化, 则重新查找.
vtable
: 提供 invokevirtual
指令调用方法定位表.
- 被
final
和static
修饰的方法不会出现在vtable
- 子类会继承父类的
vtable
- Object 类有 5 个虚方法. 所以一个对象的 vtable 至少是 5
- hashCode
- equals
- clone
- toString
- finalize
itable
: 提供 invokeinterface
: 指令 调用方法定位表
Lambda 表达式的原理
匿名内部类在编译期间生成新的 class 文件来实现的.
而 lambda 而是使用 invokedynamic
来实现的
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
invokedynamic
指令后面的 #N
表示从 BootstrapMethods:
中的N
索引中的方法. 对应的是调用
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
caller
: 表示 JVM 提供的查找上下文invokedName
: 表示调用函数名samMethodType
: 函数式接口定义的方法签名implMethod
: 编译时生成的 Lambda 表达式对应的静态方法instantiatedMethodType
: 一般和 samMethodType 是一样或是它的一个特例
整个流程如下
- Lambda 的地方会生成一个
invokeddynamic
指令, 同时编译器生成一个对应的bootstrap method
- 第一次执行
invokeddynamic
指令时, 会调用对应的bootstrap method
, 它会调用LambdaMetafactory.metafactory
方法动态生成内部类 bootstrap method
会返回一个动态调用CallSite
对象, 它会最终调用实现了接口的内部类Lambda
表达式中的内容会被编译成静态方法, 前面生成的动态内部类会直接调用该方法- 真正执行 lambda 调用的还是调用
invokeinterface
指令
要想获取运行期生成的内部类, 可以添加 JVM 参数 -Djdk.internal.lambda.dumpProxyClasses=<path to dump directory>
即可.
package io.emacsist.github.pdf;
public class TestConst {
public static void main(String[] args) {
Runnable r1 = () -> {
System.out.println("lambda");
};
r1.run();
}
}
然后运行 java -Djdk.internal.lambda.dumpProxyClasses=/tmp/lambda/ io.emacsist.github.pdf.TestConst
最后, 可以在目录 /tmp/lambda
中看到生成的内部类了
tree /tmp/lambda
/tmp/lambda
└── io
└── emacsist
└── github
└── pdf
└── TestConst$$Lambda$1.class
反编译生的代码为
final class io.emacsist.github.pdf.TestConst$$Lambda$1 implements java.lang.Runnable {
private io.emacsist.github.pdf.TestConst$$Lambda$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void run();
Code:
0: invokestatic #17 // Method io/emacsist/github/pdf/TestConst.lambda$main$0:()V
3: return
}
可以看到, 新生成的内部类的 run
方法就直接调用了静态方法.
泛型与字节码
package io.emacsist.github.pdf;
import java.util.List;
public class TestConst {
public void say(List<String> hello) {
String a = hello.get(0);
System.out.println(a);
}
}
生成的字节码为
public void say(java.util.List<java.lang.String>);
descriptor: (Ljava/util/List;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: aload_1
1: iconst_0
2: invokeinterface #2, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7: checkcast #3 // class java/lang/String
10: astore_2
11: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
14: aload_2
15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 8: 0
line 9: 11
line 10: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lio/emacsist/github/pdf/TestConst;
0 19 1 hello Ljava/util/List;
11 8 2 a Ljava/lang/String;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 19 1 hello Ljava/util/List<Ljava/lang/String;>;
Signature: #24 // (Ljava/util/List<Ljava/lang/String;>;)V
- 泛型附加信息对 JVM 来说是不可见的. 泛型是在 Javac 中实现的, 生成的字节码中, 已经不包含泛型信息. 在使用时加上类型, 编译时再抹掉, 这称为泛型擦除
- 泛型并没有自己独有的 class 类对象
- 泛型不能用 primary 原始类型来实例化参数
- Java 不能捕获泛型异常
- 不能声明泛型数组
synchronized 的字节码
package io.emacsist.github.pdf;
public class TestConst {
int i = 0;
public void incr() {
synchronized (this) {
i++;
}
}
}
生成的字节码为
public void incr();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
Exception table:
from to target type
4 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 4
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lio/emacsist/github/pdf/TestConst;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class io/emacsist/github/pdf/TestConst, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
可以看到, 由编译器生成的字节码, 会保证 monitorenter
后, 一定会执行 monitorexit
反射
Inflation 机制 . https://blogs.oracle.com/buck/inflation-system-properties
它由两个参数影响
-Dsun.reflect.inflationThreshold=N
: 默认为 15. 表示反射的方法将会有 N 次调用是通过 JNI 的方式实现的-Dsun.reflect.noInflation=true|false
: 默认为 false . 如果为 true, 则直接使用动态生成的类的方式来调用反射
javac 编译原理
7 个阶段
- parse : 词法和语法分析.
- 词法分析:
com.sun.tools.javac.parser.Scanner
实现 - 语法分析:
com.sun.tools.javac.parser.JavacParser
实现
- 词法分析:
- enter : 解析和填充符号表. 没默认构造函数, 则添加默认. 主要由以下实现
com.sun.tools.javac.comp.Enter
com.sun.tools.javac.comp.MemberEnter
- process : 注解处理. 由
com.sun.tools.javac.processing.JavacProcessingEnvironment
完成 - attribute : 做语义合法性检查, 常量折叠等.
- flow : 处理数据流分析. 主要由
com.sun.tools.javac.comp.Flow
类实现.- 非 void 方法所有退出分支是否都有返回值
- 检查受检异常是否被捕获或显式抛出
- 检查局部变量是否被初始化
- 检查 final 是否有重复赋值
- 检查语句是否有不可达
- desugar : 解除语法糖
- generate : 生成 class 文件. 由
com.sun.tools.javac.jvm.Gen
类实现
switch 的选择
// Determine whether to issue a tableswitch or a lookupswitch
// instruction.
long table_space_cost = 4 + ((long) hi - lo + 1); // words
long table_time_cost = 3; // comparisons
long lookup_space_cost = 3 + 2 * (long) nlabels;
long lookup_time_cost = nlabels;
int opcode =
nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost
?
tableswitch : lookupswitch;
选择 tableswitch O(1)
还是 lookupswitch O(log n)
, 是根据上面的”代价”来选择的
hi
表示 case 的最大值lo
表示 case 的最小值
字节码操作工具
asm
性能最好, 但要求高
javassist
相对简单点. 但性能不如 asm
Instrumentation
使用方式
- 通过 agent :
java -javaagent:myagent.jar MyMain
. 它要在 jar 包的MANIFEST.MF
文件指定相关的信息 基于 attach 方式:
VirtualMachine jvm = VirtualMachine.attach(jvmPid); jvm.loadAgent(agentFile.getAbsolutePath()); jvm.detach();
创建一个 agent
- 创建一个类, 假设为 MyAgent
- 添加一个静态方法, 签名为
public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception
- 第一个参数是 agent 的启动参数. 写法如下
java -javaagent:xxxx.jar=key:value,key2:value2
- 第二个参数是 instrumentation 的实例
- 添加一个类
MyClassFileTransformer
实现ClassFileTransformer
接口进行 class 文件转换 - 然后在
instrumentation
实例中调用addTransformer(new MyClassFileTransformer(), true)
即可
- 第一个参数是 agent 的启动参数. 写法如下
打包. (要添加 manifest 信息)
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> <configuration> <archive> <manifestEntries> <Premain-Class>xxx.MyAgent</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins>
Attach 的方式的话, 它调用的是 public static void agentmain(String agentArgument, Instrumentation instrumentation) throws Exception
参考资料
https://www.throwable.club/2019/06/29/java-understand-instrument-first/
JSR 269 插件化注解处理原理
在前面的 javac 阶段中的 process 阶段. JSR 269 就发生在这阶段.
字节码的应用
cglib
asm : 字节码改写
cglib : 动态代理
编写步骤
- 实现
MethodInterceptor
- 调用
Enhancer.create
- 通过代理类实例调用目标方法
要想查找 cglib 生成的类, 可以设置属性让 cglib 保存到磁盘文件中
-Dcglib.debugLocation=/tmp/cglib/
cglib 不同于 JDK 动态代理使用反射来调用被拦截的方式, cglib 使用 FastClass 来避免反射. 它的原理是对被代理类的方法增加索引, 通过索引来定位具体方法.
fastjson
使用 asm 为对应的 bean 生成类, 规避反射获取类字段的操作
dubbo
Javassit 生成接口的代理类
jacoco
使用 asm 实现覆盖率统计功能
mockito
使用 cglib 生成子类实现 mock 的功能