一、概述

字节码文件的跨平台性

  • Java语言,跨平台语言
    • 编译后的class文件,在不同平台上运行
    • 现在阶段出现的高级语言,基本都是跨平台的语言了,已经成为一门语言的的必选特征了
  • Java虚拟机,跨语言平台
    • 不与语言进行捆绑,而是和class文件进行捆绑,只要遵循class规范,不论什么语言都可以运行在JVM上
    • 想要让一个java程序正确的在JVM中运行,源码就必须编译成符合规范的字节码
      • 前端编译:java源码->class文件
      • javac 常用工具之一:词法解析、语法解析、语义解析、生成字节码
  • 前端编译器
    • 主要作用就是将java源码编译成class文件
    • 可以使用任何前端编译器,javac只是官方提供的一种是全量编译,也有些是部分编译
    • 前端编译器不会进行编译优化

透过字节指令看代码细节

1、举例1

2、举例2

3、举例3

二、虚拟机的基石-class文件

  • 字节码存放的内容就是字节码指令,也就是JVM的指令,不是机器吗
  • 字节码指令 = 操作码 + 操作数(bipush 30),也可能只有操作码
  • 解读字节码的三种方式
    • 使用jclasslib插件或者客户端等都可以
    • 直接使用Notepad++文本编辑器阅读二进制文件
    • 使用javap指令,jdk自带

三、Class文件结构

class本质其实是一个二进制流数据,可以保存在本地,也可以通过网络传输。由于class文件的格式中没有间隔符号,所以不论是数量还是顺序都要严格规定好。

Class文件格式只有2中属类型:

  • 无符号数:基本数据类型,以u1、u2、u4、u8代表1、2、4、8个字节,通常表示数字、索引引用、数量值或者UTF8构成的字符串
  • 表:符合数据类型,可以理解为数组,“_info”表示,方法表、字段表等

结构:

  • 魔数:识别文件是class文件,cafebabe
  • class版本:jdk版本,主版本.副版本,没升级jdk主版本就都+1,class文件是向下兼容的,低版本无法执行高版本的编译的class指令
  • 常量池:常量池计数器+常量池表,存放常量的地方,内容最丰富的地方,也是class的资源仓库,字段和方法的相关信息也存放在常量池中,可以说是常量池是class文件的基石。
    • 常量池表,主要存放了字面量和引用符号,这部分在类加载完毕后会进入方法取的常量池中(JDK1.7后,字符串常量池和静态常量池存放在堆中)
      • 字面量分为文本字符串和生命为final的常量值
      • 符号引用
        • 类和接口的全限定名:com/xx/Demo
        • 字段名称和描述符、方法名称和描述符
        • 可以想到,在Class中存放着字段和方法的符号引用,在加载的过程中,会把这些符号引用转换为直接引用,并指向响应的内存地址,也就是动态链接的过程
        • 方法和变量的引用最后也指想到字面量
    • 常量池计数器为1的时候,常量池表中的数据为0,0空出来了,因为在其他地方引用常量池的时候,不想引用任何东西的时候,可以直接指向0位置
    • 字符串长度不确定,所以存在一个标示长度的u1
  • 访问标志:接口or类,在常量池之后,紧跟的就是访问标志用一个u2表示
    • 在字节码中,访问标志=所有权限的集合
    • 存在ACC_INTERFACE就是接口,否则就是类
  • 类索引、父类索引、接口索引集合:类的相关信息
  • 字段表集合(fileds):计数器+表
    • 用于描述接口或者类中生命的变量,字段包括类级变量以及实例变量,但是不包括方法内部、代码块内部声明的局部变量
    • 字段名字叫什么、被定义为什么数据类型,这都是无法固定的,只能引用常量池中的常量来描述
    • 它指向常量池索引集合,他描述了每个字端的完整信息。如字端的标识符、访问修饰符、是否静态、是否常量
    • 集成的字段不会列出来,字段是无法重载的
    • 结构如下,字段计数器,表中每个元素包含字段访问标志(字段权限)、字段名索引(字段名称)、描述符索引(字段类型)、字段的属性计数器、字段属性表
    • 一个字端可能存在一些属性,存放变量的额外信息,如常量存在属性ConstantValue
  • 方法表集合:计数器+表
    • 每个method_info都代表一个方法或者接口信息,包含方法的修饰符、参数、返回值类型等
    • 描述中不包含继承的方法,同时也可能是编译器自动添加的如<clint>()和实力初始化方法<init>()
    • 特征签名=方法名+参数,不关心返回值
    • methods[]
      • methods表中每个成员都是一个method_info结构,用户表示当前类或接口中的某个方法的完整描述
      • method_info中表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化和类或接口初始化方法
      • 方法表的结构如下
      • 结构很多,那code(源码)属性举例
        • LineNumberTable:描述java源码和字节码行号的对应关系,start_pc=字节码行号,line_number源码行号
        • LocalVariableTable(局部变量表):start_pc=位置、Length=长度15、Index=索引号、Name=名称、Descripor=描述符
  • 属性表集合:计数器+表,表示类的属性如:类名称等 
    • 指class文件携带的辅助信息,在字段表和方法表里面也有自己的属性信息
    • 限制严格、也没有顺序、可以定义自己的属性信息,jvm不认识就不识别
    • 通用合适:属性名索引、属性长度、内容

小结,class整体的结构基本不会因为jdk升级而做大的调整,整体是比较固定的。从JVM角度查看,会让更多语言支持,只要按照要求生成复合格式的class文件就可以被JVM识别并运行。

四、使用javap指令解析class文件

javap是官方提供的反编译指令。

javac -g 才会生成局部变量表,不加 -g 参数就不会编译局部变量表。

javap的参数:

javap中输出不包含私有信息,需要的话使用javap -v -p x.class

  • 通过javap指令可以查看类反编译得到Class的版本号、常量池、访问标识、变量表、指令代码等信息。不现实类索引、父类索引、接口接口索引、<init>、<clinit>等结构
  • 通过javap可以知道,一个方法执行通常会涉及下面几块内存的操作:
    • java栈中,局部变量表、操作数栈
    • java堆中,通过对象的地址引用去操作
    • 常量池
    • 其他如帧数据区、方法区的剩余部分情况
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐