1-1 对象/实例初始化(字节码层面)
1-1-1 静态成员初始化:cinit()V
静态变量与静态代码块
public class Demo3_8_1 {
static int i = 10;
static {
i = 20;
} static {
i = 30;
}
}
静态变量初始化字节码(代码块与单独的静态变量会收集在一起)
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 10
2: putstatic #2 // Field i:I
5: bipush 20
7: putstatic #2 // Field i:I
10: bipush 30
12: putstatic #2 // Field i:I
15: return
LineNumberTable:
line 4: 0
line 6: 5
line 8: 10
line 9: 15
总结:无论是静态变量还是静态代码块,编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法:
- 上面静态变量的最终值最后一次赋值为30。
1-1-2 实例的初始化: init()V
test1类
package part3;
public class test1 {
private String a = "s1";
{
b = 20;
}
private int b = 10;
{
a = "s2";
}
public test1(String a, int b) {
this.a = a;
this.b = b;
}
public static void main(String[] args) {
test1 d = new test1("s3",30);
System.out.println(d.a); // s3
System.out.println(d.b); // 30
}
}
类初始化对应的字节码(字节码层面将代码块与构造函数的字节码统一到init:V内部)
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String s1
7: putfield #3 // Field a:Ljava/lang/String;
10: aload_0
11: bipush 20
13: putfield #4 // Field b:I
16: aload_0
17: bipush 10
19: putfield #4 // Field b:I
22: aload_0
23: ldc #5 // String s2
25: putfield #3 // Field a:Ljava/lang/String;
28: aload_0
29: aload_1
30: putfield #3 // Field a:Ljava/lang/String;
33: aload_0
34: iload_2
35: putfield #4 // Field b:I
38: return
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
0 39 0 this Lcn/itcast/jvm/t3/bytecode/Demo3_8_2;
0 39 1 a Ljava/lang/String;
0 39 2 b I
MethodParameters: ...
- 编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后
1-2 方法初始化(动态绑定与静态绑定在字节码层面的体现)
源码
package part3;
public class demo {
public demo(){}
private void test1(){}
private final void test2(){}
public void test3(){}
public static void test4(){}
protected void test5(){};
void test6(){};
public static void main(String[] args) {
demo d = new demo();
d.test1();
d.test2();
d.test3();
d.test4();
demo.test4();
d.test5();
d.test6();
}
}
main函数字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class part3/demo 创建未初始化的对象引用
3: dup // 复制栈顶对象引用,并入栈
4: invokespecial #3 // Method "<init>":()V ,调用对象初始化方法,初始化实例,
7: astore_1 // 存储实例引用
8: aload_1 // 加载实例引用
9: invokespecial #4 // Method test1:()V
12: aload_1
13: invokespecial #5 // Method test2:()V
16: aload_1
17: invokevirtual #6 // Method test3:()V
20: aload_1
21: pop // 静态方法不需要用到实例对象,所以将实例对象从操作数栈弹出
22: invokestatic #7 // Method test4:()V
25: invokestatic #7 // Method test4:()V
28: aload_1
29: invokevirtual #8 // Method test5:()V
32: aload_1
33: invokevirtual #9 // Method test6:()V
36: return
LineNumberTable:
line 12: 0
line 13: 8
line 14: 12
line 15: 16
line 16: 20
line 17: 25
line 18: 28
line 19: 32
line 20: 36
LocalVariableTable:
Start Length Slot Name Signature
0 37 0 args [Ljava/lang/String;
8 29 1 d Lpart3/demo;
}
SourceFile: "demo.java"
1-2-1方法调用的字节码指令总结(重要)
命令 | 适用的函数 | 特点 |
---|---|---|
invokespecial | private和构造函数,以及final修饰函数 | 静态绑定函数,转换为字节码的时候就已经确定 |
invokestatic | static修饰的函数 | 静态绑定函数,转换为字节码的时候就已经确定 |
invokevirtual,invokeinterface(通过接口引用调用方法) | public和protected修饰的函数,默认的函数 | 动态绑定函数,链接的时候根据vtable查找执行方法的字节码 |
1-3 多态的原理
工具的具体使用可以参考:Java字节码角度分析多态原理 ——提升硬实力8
1-3-1 简单的多态实例:通过父类引用子类对象。
package part3;
import java.io.IOException;
/**
* 演示多态原理,注意加上下面的 JVM 参数,禁用指针压缩,指针压缩会将类的实际地址进行压缩。
* -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
*/
public class test2 {
public static void test(Animal animal) {
animal.eat();
System.out.println(animal.toString());
}
public static void main(String[] args) throws IOException {
test(new Cat());
test(new Dog());
System.in.read();
}
}
abstract class Animal {
public abstract void eat();
@Override
public String toString() {
return "我是" + this.getClass().getSimpleName();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("啃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("吃鱼");
}
}
执行结果
吃鱼
我是Cat
啃骨头
我是Dog
原理:Java多态的实现是通过方法区的类的方法表(vtable)查表确定具体调用哪一个方法,方法的确定是在类加载的过程中链接时确定的,因此称为动态绑定。
1-3-2 多态原理总结(重要)
注意:动态绑定的函数才叫多态。
当执行 invokevirtual 指令时,
1. 先通过栈帧中的对象引用找到对象
2. 分析对象头,找到对象的实际 Class(注意对象头中包含mark word和Klass word,Kclass word是一个指针,指向对象所从属的class)
3. Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
4. 查表得到方法的具体地址
5. 执行方法的字节码
1-3-3 学会借助HSDB 工具查看类的结构
操作步骤
step1: 运行查看程序,通过jps获取程序的ID,然后运行HDSB工具。
在JDK包安装目录下使用该命令:java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
C:Program FilesJavajdk1.8.0_131>java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
step2:根据进程PID,让工具连接进程(attach to hotspot process)。
step3:通过tools->class browser找到对应的Dog class
1-4 字节码层面的异常的处理体现
1-4-1 try-catch 字节码命令实例
源码+code部分字节码
public class test3 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
}
}
}
========================================================================================================================
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1 // 2,4行作用即 i = 10
5: goto 12
8: astore_2 // astore_2 是将异常对象引用存入局部变量表的 slot 2 位置
9: bipush 20
11: istore_1 // 9,11作用 即 i = 20
12: return
Exception table: // 该处代码的异常表,检测范围是从from到to即[2,5),在这个范围内的代码出现异常都会被检测
from to target type
2 5 8 Class java/lang/Exception
LineNumberTable:
line 5: 0
line 7: 2
line 10: 5
line 8: 8
line 9: 9
line 11: 12
LocalVariableTable: // 局部变量表中slot2用于存储异常对象的引用
Start Length Slot Name Signature
9 3 2 e Ljava/lang/Exception;
0 13 0 args [Ljava/lang/String;
2 11 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
}
总结:
- 字节码指令通过Exception table 的结构检测固定范围内的字节码执行异常。
- 如果出现异常需要匹配异常的类型,并使得指令跳转到执行位置。
- 没有异常,在上面的示例中catch代码块由于go to 命令不会执行。
1-4-2 单个try多个catch的异常处理
源码+code部分字节码
public class test4 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (ArithmeticException e) {
i = 30;
} catch (NullPointerException e) {
i = 40;
} catch (Exception e) {
i = 50;
}
}
}
======================================================================================================
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 26
8: astore_2
9: bipush 30
11: istore_1
12: goto 26
15: astore_2
16: bipush 40
18: istore_1
19: goto 26
22: astore_2
23: bipush 50
25: istore_1
26: return
Exception table:
from to target type
2 5 8 Class java/lang/ArithmeticException
2 5 15 Class java/lang/NullPointerException
2 5 22 Class java/lang/Exception
LineNumberTable:
line 5: 0
line 7: 2
line 14: 5
line 8: 8
line 9: 9
line 14: 12
line 10: 15
line 11: 16
line 14: 19
line 12: 22
line 13: 23
line 15: 26
LocalVariableTable: // 三种类型的异常无论哪种发生都只会存储在局部变量表的slot2位置
Start Length Slot Name Signature
9 3 2 e Ljava/lang/ArithmeticException;
16 3 2 e Ljava/lang/NullPointerException;
23 3 2 e Ljava/lang/Exception;
0 27 0 args [Ljava/lang/String;
2 25 1 i I
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/ArithmeticException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/NullPointerException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
}
SourceFile: "test4.java"
总结:单个try多个catch情况下,异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用 。
1-4-3 multi-catch 的情况(JDK1.7新支持的语法)
源码+code部分字节码
package part3;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test5 {
public static void main(String[] args){
try{
Method test = test5.class.getMethod("test");
test.invoke("test");
}catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e){
e.printStackTrace();
}
}
public static void test() {
System.out.println("ok");
}
}
===========================================================================================================s
public part3.test5();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpart3/test5;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: ldc #2 // class part3/test5
2: ldc #3 // String test
4: iconst_0
5: anewarray #4 // class java/lang/Class
8: invokevirtual #5 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
11: astore_1
12: aload_1
13: ldc #3 // String test
15: iconst_0
16: anewarray #6 // class java/lang/Object
19: invokevirtual #7 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
22: pop
23: goto 31
26: astore_1 // 将异常引用存入到局部变量表的slot1
27: aload_1 // 将异常引用放入操作数栈
28: invokevirtual #11 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
31: return
Exception table: // 异常表,发生异常跳转到同一target位置(标号为26的位置)
from to target type
0 23 26 Class java/lang/NoSuchMethodException
0 23 26 Class java/lang/IllegalAccessException
0 23 26 Class java/lang/reflect/InvocationTargetException
LineNumberTable:
line 9: 0
line 10: 12
line 13: 23
line 11: 26
line 12: 27
line 14: 31
LocalVariableTable: // 局部变量表,共有2个slot,可以看到slot被方法的引用以及异常对象的引用共享
Start Length Slot Name Signature
12 11 1 test Ljava/lang/reflect/Method;
27 4 1 e Ljava/lang/ReflectiveOperationException;
0 32 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 90 /* same_locals_1_stack_item */
stack = [ class java/lang/ReflectiveOperationException ]
frame_type = 4 /* same */
public static void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String ok
5: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 16: 0
line 17: 8
}
SourceFile: "test5.java"
总结:新增的语法,虽然有多个异常,但最多只有一个异常发生,通过将异常表中的target指向同一个目标实现异常的捕获。
Exception table: // 异常表,发生异常跳转到同一target位置(标号为26的位置)
from to target type
0 23 26 Class java/lang/NoSuchMethodException
0 23 26 Class java/lang/IllegalAccessException
0 23 26 Class java/lang/reflect/InvocationTargetException
1-4-4 finally关键字在字节码层面的体现
public class test6 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}
============================================================================================
public part3.test6();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpart3/test6;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1 // 情况1:没有异常发生后,执行finally内代码。
5: bipush 30 // finally块内字节码第一个次出现
7: istore_1
8: goto 27
11: astore_2
12: bipush 20
14: istore_1 // 情况2:catch匹配到的异常后,执行finally内代码。
15: bipush 30 // finally块内字节码第二次出现
17: istore_1
18: goto 27
21: astore_3 // 情况3:catch无法匹配的异常的处理后,执行finally内代码
22: bipush 30 // finally块内字节码第三次出现
24: istore_1
25: aload_3 // 无法匹配的异常会存入到局部变量表的slot3后,然后抛出。
26: athrow // athrow命令会抛出异常
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any
11 15 21 any
LineNumberTable:
line 4: 0
line 6: 2
line 10: 5
line 11: 8
line 7: 11
line 8: 12
line 10: 15
line 11: 18
line 10: 21
line 12: 27
LocalVariableTable:
Start Length Slot Name Signature
12 3 2 e Ljava/lang/Exception;
0 28 0 args [Ljava/lang/String;
2 26 1 i I
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/Exception ]
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */
}
SourceFile: "test6.java"
总结:finally块内代码对应的字节码被重复了三次,分别放在了三个位置,从而确保finally块内代码一定会执行。
1-4-5 练习题:finally代码块的辨析(不要再finally里面return,会吞掉异常)
问题:下面代码的返回结果是10还是20?
源码+code部分字节码
public class test7 {
public static void main(String[] args) {
int result = test();
System.out.println(result); // 20
}
public static int test() {
try {
return 10;
} finally {
return 20;
}
}
}
============================================================================================
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: bipush 10 // 将10放入栈顶
2: istore_0 // 弹出10放入slot 0
3: bipush 20 // 将20放入栈顶 (finally代码块)
5: ireturn // 返回栈顶20
6: astore_1 // 如发生异常,把异常的引用存入到slot 1
7: bipush 20 // 将20放入栈顶
9: ireturn // 返回栈顶元素 (finally代码块)
Exception table:
from to target type
0 3 6 any
LineNumberTable:
line 10: 0
line 12: 3
StackMapTable: number_of_entries = 1
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
}
SourceFile: "test7.java"
总结:可以看到 return 10 这个语句在字节码层面的表现如下所示,并没有返回,因为必须执行finally语句之后才能返回。
0: bipush 10 // 将10放入栈顶
2: istore_0 // 弹出10放入slot 0
- 操作数栈顶元素的返回必须执行完finally语句的字节码指令。通过将 finally 中的 ireturn 被插入了所有可能的流程,返回结果肯定以 finally 的为准
- 1-4-4代码中finally的字节码会有athrow字节码抛出异常而这个例子里没有,这启示我们如果在 finally 中出现了 return,会吞掉异常,这个非常危险。
实例:finally里面return会导致程序的异常无法被捕获
public class test7 {
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
try {
int i = 1/0;
return 10;
} finally {
return 20;
}
}
}
1-4-6 练习题:finally代码块的辨析
问题:下面代码的返回结果是10还是20?
package part3;
public class test8 {
public static void main(String[] args) {
int result = test();
System.out.println(result); // 10
}
public static int test(){
int i = 10;
try {
return i;
} finally {
i = 20;
}
}
}
=================================================================================================
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: bipush 10
2: istore_0 // 局部变量表的slot0:10
3: iload_0
4: istore_1 // 弹出,存储到局部变量表的slot1:10
5: bipush 20 // finally 语句第二次出现
7: istore_0 //弹出,存储到局部变量表的slot0:20
8: iload_1
9: ireturn // 没有异常发生,直接将局部变量表的slot 1的值即10返回
10: astore_2 // 局部变量表的slot2:发生的异常对象引用
11: bipush 20 // finally 语句第一次出现,此时的修改无法影响返回值
13: istore_0 // 弹出,存储到局部变量表的slot0:20
14: aload_2 // 有异常发生,加载异常对象引用
15: athrow // 抛出异常
Exception table:
from to target type
3 5 10 any
LineNumberTable:
line 10: 0
line 12: 3
line 14: 5
line 12: 8
line 14: 10
LocalVariableTable:
Start Length Slot Name Signature
3 13 0 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ int ]
stack = [ class java/lang/Throwable ]
}
SourceFile: "test8.java"
总结:在finally的对i的修改与返回的i的值在局部变量表的不同slot
1-5 synchronized关键字在字节码层面的体现
实例源码与code属性字节码
public class test9 {
public static void main(String[] args) {
Object lock = new Object();
synchronized (lock) { /*问题:字节码层面如何保证锁的进入与退出成对?*/
System.out.println("ok");
}
}
}
===========================================================================
{
public part3.test9();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpart3/test9;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object 创建object对象,并将对象引用压入操作数栈
3: dup // 复制对象引用压入操作数栈
/*2个对象引用作用(对应4,7,8)
1)一个弹出用于invokespecial,用于调用对象的构造方法
2)另外一个弹出放入到局部变量表的slot1即lock变量存储对象的引用
*/
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
/* 2个对象引用作用(对应4,7,8):
1)一个弹出用于monitorenter,加锁
2)另外一个弹出存储到局部变量表之后用于monitorexit,解锁。
*/
10: astore_2 // 弹出,局部变量表slot2:锁对象引用
11: monitorenter // 加锁
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String ok
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit // 解锁
22: goto 30 // 没有发生异常则返回
/*
synchronized代码块内异常处理。
*/
25: astore_3 // 存储异常引用到局部变量表slot3
26: aload_2 // 加载 局部变量表slot2:锁对象引用 到操作数栈
27: monitorexit // 异常情况下保证解锁!!!!!!!!!!!
28: aload_3 // 加载异常引用到操作数栈
29: athrow // 抛出异常
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any // 25-28如果一直发生异常会一直执行,确保解锁。
LineNumberTable:
line 5: 0
line 6: 8
line 7: 12
line 8: 20
line 9: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class "[Ljava/lang/String;", class java/lang/Object, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "test9.java"
总结:锁的进入与退出需要考虑发生异常与不发生异常两种情况。
参考资料
02 JDK8:Chapter 6. The Java Virtual Machine Instruction Set