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代码块底层原理的主要内容,如果未能解决你的问题,请参考以下文章

Java虚拟机学习笔记——类文件结构与字节码指令

黑马程序员JVM教程笔记完整目录

黑马程序员JVM教程笔记完整目录

是否有 java 类文件/字节码编辑器来编辑指令? [关闭]

JVM第四卷--类加载与字节码技术

JVM进阶之路十二:字节码指令