JVMday03类文件结构 字节码指令 多态的原理 异常 synchronized代码块底层原理
Posted halulu.me
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVMday03类文件结构 字节码指令 多态的原理 异常 synchronized代码块底层原理相关的知识,希望对你有一定的参考价值。
目录
类文件结构
查看二进制字节码的命令(linux)
类文件结构
1、魔术(magic)
不同的文件有自己的魔术信息,魔术就是标识这个文件属于什么类型。而java文件的魔术就是cafebabe,占4个字节(u4)。
2、版本
minor_version 小版本:00 00 (u2)
major_version 主版本:00 34 (u2)
00 34 的十进制就是52,代表的是jdk1.8
3、常量池(constant_pool)
常量池中包括各种信息:方法的信息,成员变量的信息,类的信息,构造器的信息,父类的信息。
常量池中的17中数据类型(详细)
<init>就是构造方法
utf-8后面2字节代表长度,代指要读几个字节。
4、访问标识(access_flags)
00 21 代表 00 01 + 00 20 ,表示这个类是public的,占2个字节
5、本类全限定名(this_class)
根据常量池查找。
Java类包的定名:com.halulu.test.Hello,从最原始最上层的地方援引到具体的对象,这就是全限定名了。
占2个字节
6、父类的全限定名(super_class)
根据常量池查找。
占2个字节
7、接口数量(interface)
占2个字节
8、成员变量(fields)
flelds_count:成员变量的数量,占2个字节
9、方法(methods)
method_count :方法数量,占2个字节
10、附加属性(attributes)
字节码指令
工具
javap -v xxx.class
aload 加载变量
getstatic 从运行时常量池中找到成员变量(堆)的引用。(并不会把成员变量直接放进栈中,而是堆中成员变量的引用放入栈中)
putstatic 将栈中的数值赋值给常量池中成员变量
bipush 将一个byte压入操作数栈中(其长度会补齐4个字节)
sipush 讲一个short压入操作数栈中(其长度会补齐4个字节)
ldc 将一个int 压入操作数栈中(找到常量池中的属性,然后压入操作数栈)
idc2_w 将一个long压入操作数栈中(分2次压入,因为long是8字节)
athrow 抛出异常
monitorenter 加锁
monitorexit 解锁
invokespecial 预备调用构造方法(静态绑定,直接确定方法)
invokevirtual 预备调用成员方法,然后会分配一个新的栈帧(动态绑定,普通方法可能会发生方法重写,所以在编译期间不能确定调用何种方法,需要在运行的时候才能确定)(vtable中)
invokerstatic 调用静态方法(静态绑定,直接确定方法)
new 在堆空间分配内存,然后把对象的引用放入操作数栈
dup 把栈顶的地址进行复制
pop出栈
istore 1 代表把栈顶中的数值放到槽位1中(slot)
iload 1 代表把槽位1中的值读取到操作数栈上
iadd 代表加法运算
iinc 直接在局部变量槽位slot上运行(iinc 1,1 第一个数组代表对哪个槽位自增,第二数代表自增多少)
小的数字和字节码指令存放在一起,超过short范围的数字存入了常量池
条件判断指令
byte、short、char会自动补齐4位字节,按int进行条件判断
循环控制指令
iconst_0 代表常量0
int 取值 0~5 时,JVM采用 iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5指令将常量压入栈中;
goto 2 代表跳到第2行代码
while和for循环的字节码指令是一样的
short类型以下的数字是跟字节码指令存储在一起的,并不会放在常量池中,short类型之上的数字才会放在常量池中,常量池随着类的加载会放入运行时常量池中。
从字节码角度分析a++ ,++a
a++ 和 ++ a的区别在于先执行iload 还是先执行 iinc.
istore_1 存放a
istore_2 存放b
案例:a = a++
public static void main(String[] args)
int a = 0;
int i = 0;
int b = 0;
while (i < 5)
a = a++; //0 ,iload , iinc , istore
b = ++b; //5 , iinc , iload ,istore
i++;
System.out.println(a);
System.out.println(b);
1、a的初始值是0,
2、iload_1将a加载到栈中,此时栈中的值为0,
3、iinc 1,1 槽位中的a+1 变为1 ,此时栈中的值依旧为0,
4、istore_1 将栈中的0顶替槽位slot中的1,此时槽位中的a就变为0了
静态的a++ 和 a–
与前面不同的是,静态的a++和a–是在操作数栈上自增自减的,而非静态的自增和自减是在槽上进行,但所得的结果一直。
构造器的原理
public class Demo2
static int a = 10;
static
a = 20;
static
a = 30;
public static void main(String[] args)
System.out.println(a); // 30
这三个static在编译阶段会合并成一个<cinit>方法,顺序是从上往下
编译器会按从上至下的顺序,收集所有 代码块和成员变量赋值的代码,形成新的构造方法<init>,但原始构造方法内的代码总是在最后。
多态的原理
工具
1、jps获取进程
2、java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
当执行invokevirtual指令时(多态):
1、通过栈帧中的对象引用找到对象
2、分析对象头,找到对象的实际class
3、class结构中有vtable(虚方法表),它在类加载的链接阶段就已经按照方法重写的规则生成
4、查表可以的得到多态方法的具体地址
5、执行方法的字节码
异常
1、单个Catch块
exception table 异常表
代码从第2行运行到第4行的时候,会跟type中的exception进行匹配,如果匹配通过,就会进入第8行,也就是catch块,如果不通过,就跳过(error或者throwable与exception是匹配不上的)。
2、多个catch块
过程跟单个catch块一致,会依次进行匹配。
3、multi-catch块
4、finally块
1、如果异常是exctption,那么会把finally块中的字节码放在try块或者catch块的后面,注意是在return块之前。(保证一定会被执行)
2、如果异常是throwable或者error(不会与exception匹配),try块或者Catch块会与any进行匹配,然后通过,运行finally块(保证一定会执行)
3、try-catch-finally会被分为3个分支:try分支,catch分支,catch匹配不到的分支。然后finally块中的代码会被复制3份,分别放入这3个分支中,保证finally块一定会被执行。
案例1:finally出现了return
案例2:finally中的return会吞掉athrow(不会抛出异常)
public static void main(String[] args)
int result = test();
System.out.println(result);
public static int test()
try
int i = 1/0;
return 10;
catch (Exception e)
return 30;
finally
return 20;
finally中的return会吞掉异常athrow,会导致代码运行并不会抛出异常,尽量不在finally中使用return。
案例3
public static void main(String[] args)
int result = test();
System.out.println(result);
public static int test()
int i = 10;
try
return i; // 10 , 返回值固定
finally
i = 20; //槽位的数值变化
返回值固定,这个时候finally仅仅只是改变了槽位上的值,并没有改变栈顶上的值,而return返回的是栈顶上的值。
synchronized代码块底层原理
public static void main(String[] args)
Object lock = new Object();
synchronized (lock)
System.out.println("ok");
1、方法级别的synchronized不会再字节码指令中体现。
2、monitorenter 与 monitorexit 加载到同个类型的锁对象才会通过
3、如果抛出异常,也会确保monitorexit 加载到同一个类型得到锁对象(确保一定会被解锁,然后抛出异常)
以上是关于JVMday03类文件结构 字节码指令 多态的原理 异常 synchronized代码块底层原理的主要内容,如果未能解决你的问题,请参考以下文章