Class文件的概述
一、概述字节码文件的跨平台性Java语言,跨平台语言编译后的class文件,在不同平台上运行现在阶段出现的高级语言,基本都是跨平台的语言了,已经成为一门语言的的必选特征了Java虚拟机,跨语言平台不与语言进行捆绑,而是和class文件进行捆绑,只要遵循class规范,不论什么语言都可以运行在JVM上想要让一个java程序正确的在JVM中运行,源码就必须编译成符合规范的字节码前端编译:java源码-
·
一、概述
字节码文件的跨平台性
- 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
- 常量池表,主要存放了字面量和引用符号,这部分在类加载完毕后会进入方法取的常量池中(JDK1.7后,字符串常量池和静态常量池存放在堆中)
- 访问标志:接口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堆中,通过对象的地址引用去操作
- 常量池
- 其他如帧数据区、方法区的剩余部分情况
更多推荐
已为社区贡献1条内容
所有评论(0)