jvm字节码


jvm字节码

jvm字节码一般结构

使用javap -verbose 命令分析一个类,得到的是一个类字节码的魔数,版本号,常量池,类信息,类的构造方法,类的方法信息,类变量与成员变量等信息。当然也可以配合jclasslib这个idea插件来学习字节码内容

  1. 魔数:.class字节码的前四个字节即为魔数。魔数为固定值:0xCAFEBABE
  2. 版本号:魔数之后的四个字节即为版本号,前两个字节为 minor version 次版本号,后两个字节为 major version 主版本号。
  3. 常量池:主版本号之后的即为常量池入口,常量池是可变的,不确定的。可以将常量池看做类文件的资源仓库。常量池中主要存储两类常量:字面常量和符号引用。字面常量如文本字符串。符号引用:类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。
    常量池由常量池数量和常量池表构成。
    • 常量池数量
      常量池数量在主版本号之后,占据两个字节。
    • 常量池表
      在常量池数量之后。常量池数组中不同元素的类型结构是不同的,因此长度不同。但是每一种元素的第一个数据都是一个u1类型,该位置是标志位,占用一个字节,jvm在解析常量池时,会根据u1类型来获取元素的具体类型。
  4. 类信息:
    • 类的访问修饰符(u2)
    • 类的名字(常量池索引)
    • 父类名字(常量池索引)
    • 接口个数
    • 接口表
  5. 成员变量:
    • 成员变量数(u2)
    • 成员变量表(包含类变量和和实例变量)
      • 权限描述符
      • 名字索引
      • 类型描述符索引
      • 属性数
      • 属性信息
  6. 方法
    • 方法个数(u2)
    • 方法表
      • 权限描述符(u2)
      • 名字索引(u2)
      • 类型描述符索引(u2)
      • 属性数(u2)
      • 属性信息
        属性信息结构
        • 属性类型索引(u2)
        • 属性信息长度(u4),本方法属性之后的信息长度
        • 最大栈深(u2)
        • 最大局部变量数(u2),包含方法的参数和this。
        • 操作码表长度(u4)
        • 操作码表(u2,java虚拟机指令集)
        • 异常表长度
        • 异常表
          • 作用的开始行
          • 作用的结束行
          • 异常出现跳转行
          • 异常名字索引
        • 属性表长度(u2)
        • 属性表:属性表是code的属性,如linenumbertype,存储源代码和字节码行号对应关系。localvariabletable,存储方法中变量的类型,名字和描述。
          • 属性名索引(u2)
          • 属性长度(u4)
          • 属性数(u2):表示有几个属性
          • linenumbertype属性内容结构:*
            {
            对应关系:u4类型,有多个,(u2-u2)表示字节码中行号和源代码行号的对应关系
            }
          • localvariabletable属性内容结构:*
            {
            局部变量在字节码中开始作用的位置:u2类型
            局部变量在字节码中作用的长度:u2类型
            局部变量名称索引:u2类型
            局部变量描述符索引:u2类型
            局部变量localvariabletable中的索引
            }
  7. 属性
    • 属性数量(u2)
    • 属性表
      • 属性名索引(u2)
      • 属性值
        属性值长度(u4)
        属性值索引(u2)

jvm class文件是线性的,但可以尝试使用json理解字节码的结构,以下为json结构。
开头*表示线性排序表示
属性带” x”表示可以有多个

{
    "*Magic Number":"0xCAFEBABE",
    "Version":{
        "*minor version":"u2",
        "*major version":"u2"
    },
    "Constant Pool":{
        "*Constant_count":"u2",
        "*Constant_info":[
            "类型众多,参照表格,基本结构为u1+n个字节。"            
        ]
    },
    "Class info":{
        "*Access Flags":"u2,类的访问修饰符",
        "*This Class Name":"u2类名",
        "*Super Class Name":"u2 父类名字",
        "*Interfaces_count":"接口数",
        "*Interfaces_name":["接口名有多个"]
    },
    "Fields":{
        "*Fields_count":"u2,成员变量数",
        "Fields_info *x":[
            {
               "*access_flags":"u2,权限修饰符",
               "*name_index":"u2,变量名称索引" ,
               "*descriptor_index":"u2,类型描述符索引",
               "*attribute_count":"u2,属性数目",
               "attribute_info *x":[
                   {}
               ]
            }
        ]

    },
    "Methods":{
        "*methods_count":"u2,方法数",
        "methods_info *x":[
            {
                "*access_flags":"u2,访问修饰符",
                "*name_index":"u2,方法名称索引",
                "*descriptor_index":"u2,类型描述符索引",
                "*attribute_count":"u2,属性数目",
                "attribute_info *x":[
                    {
                        "*attribute_name_index":"u2,属性名索引",
                        "*attribute_length":"u4,属性长度,指之后数据长度",
                        "*max_stack":"u2,最大栈深",
                        "*max_locals":"u2,最大局部变量数",
                        "*code_length":"u4,操作码长度",
                        "code":[
                            "*code1",
                            "*code2",
                            "*...code为u1组成"
                        ],
                        "*exception_table_length":"u2,异常表长度",
                        "exception_table":[
                            {
                            "*Start_Pc":"u4,异常开始作用行",
                            "*Eed_Pc":"u4,异常结束作用行",
                            "*Handler_Pc":"u4,异常发生跳转行",
                            "*Exception_name_index":"u2,异常名索引"
                            }

                        ],
                        "*attribute_count":"u2,属性数目",
                        "attribute_info *x":[
                            {
                                "*attribute_name_index":"u2,属性名索引",
                                "*attribute_length":"u4,属性长度,指之后数据长度",
                                "*attribute_count":"表示有几个对应关系",
                                "attribute_info":[
                                    {
                                        "*linenumbertype":"字节码和源码行号对照表,u4类型,有多个,(u2-u2)表示字节码中行号和源代码行号的对应关系"
                                    },
                                    {
                                        "localvariabletable":"局部变量表",
                                        "*局部变量在字节码中开始作用的位置":"u2类型",
                                        "*局部变量在字节码中作用的长度":"u2类型",
                                        "*局部变量名称索引":"u2类型",
                                        "*局部变量描述符索引":"u2类型",
                                        "*局部变量localvariabletable中的索引":"u2"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    },
    "Attributes":{
        "*attribute_count":"u2,属性数目",
        "attribute_table":[
            {
            "*attribute_name_index":"u2,属性名索引",
            "*attribute_length":"属性值长度",
            "*attribute_index":"属性值索引"
            }

        ]
    }    
}

synchronized 关键字

  • synchronized方法 在方法上加标识符

  • synchronized静态方法 在方法上加标识符

  • synchronized代码块 编译出不同两个指令集,在中间的表示为同步的代码块。

    静态变量和一般变量

  • 静态变量和静态代码块的指令集都在clinit()方法中执行

  • 实例变量的值和代码块都在init() 方法中执行

    字节码中的异常

  • jdk统一采用异常表的方式处理异常

  • 当异常处理存在finally语句块时,现代化的jvm采取的方式是将finally语句块的字节码拼接到每一个catch语句块后面,有多超catch语句块,就拼接多少次。

  • 方法后throws的异常会在method_info下生成一个异常数组

    栈帧和操作数栈,直接引用和间接引用

  • 栈帧是一种帮助虚拟机执行方法调用与方法执行的数据结构。

  • 栈帧本身是一种数据结构,封装了方法的局部变量表,动态链接信息,方法的返回值和操作数栈等信息。

  • 有些符号引用在类加载阶段或第一次使用时就转换为直接引用,另一些符号引用则是在每次运行期转为直接引用。这种转换叫做动态链接,在java中体现为多态性。

  • 栈帧结构
    {
    局部变量表
    操作数栈
    动态链接
    返回地址
    帧数据区
    }

    重写和重载的不同

  • 分派:确定执行哪个方法的过程

  • 静态类型:引用类型,不会被改变、在编译器可知。

  • 动态类型:实例对象类型,会变化、在运行期才可知

  • 静态分派:调用重载的方法。调用同一个类的不同的方法,编译后会指向不同的符号引用(方法的参数不同,确定不同方法),这是显而易见的,从代码就可以看出方法调用的不同。

  • 动态分派:调用重写的方法,若有两个父类引用指向不同的两个子类对象,编译后指向相同的符号引用,具体执行哪个实例方法是由jvm决定的。

  • invokevirtual:只要是调用public方法,编译后都是invokevirtual指令操作,

    • invokevirtual指令执行的第一步 = 确定接受者的实际类型
    • invokevirtual指令执行的第二步 = 将常量池中类方法符号引用解析到不同的直接引用上
  • 调用重载的方法也是invokevirtual指令,这是因为重载的方法也有可能会被重写,所有的public方法都采用invokevirtual指令,也是为了更好的体现多态,多态在jvm层面是由invokevirtual指令实现的

  • 动态绑定具体的调用过程为:

    1. 首先会找到被调用方法所属类的全限定名
    2. 在此类的方法表中寻找被调用方法,如果找到,会将方法表中此方法的索引项记录到常量池中(这个过程叫常量池解析),如果没有,编译失败。
    3. 根据具体实例化的对象找到方法区中此对象的方法表,再找到方法表中的被调用方法,最后通过直接地址找到字节码所在的内存空间。

      栈指令集和寄存器指令集

  • jvm是基于栈的指令集,基于栈的指令集主要的操作有入栈和出栈两种

  • 基于栈的指令集可以在不同平台之间移植,基于寄存器的指令集是与硬件相关的。

  • 基于栈的指令集的缺点在于完成相同操作,指令的数量多运行速度慢。基于寄存器的指令集速度则要快得多。

    动态代理

  • 在java基础中了解到了动态代理这种模式,在jvm层面,动态代理的意思是代理类的字节码是在运行期由jvm生成。

  • 生成的代理类里面,最终还是会调用自己写的InvocationHandler实现类里面的invoke方法,


Author: 向天歌
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source 向天歌 !
  TOC