代码编译的结果从本地机器码转变为字节码
1、概述
传统的将程序编写后需要进行编译成本地机器码,计算机只识别0和1,为了跨平台、跨语言,一种特殊的存储格式字节码出现了,编译的后的结果不再是机器码而是字节码。它可以被载入虚拟机当中。
2、Java语言规范、Java虚拟机规范
Java虚拟机是运行着操作系统之上的一个应用程序,在虚拟机之上不仅仅可以运行Java程序,还可以运行其它的程序。
3、Class类文件结构
解析Class文件的数据结构
以Java虚拟机规范为主线(指令、属性)
tips:任何一个Class文件都对应着唯一一个类或接口定义的信息,但是反过来说,类或接口并不一定都得定义在文件里(比如类或接口也可以通过类加载器直接生成。)
Class文件是一组以8位字节为基础单位的二进制流,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据, 这种伪结构中只有两种数据类型: 无符号数和表
无符号数:
无符号数属于基本的数据类型, 以u1、 u2、 u4、 u8来分别代表1个字节、 2个字节、 4个字节和8个字节的无符号
数, 无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值。
表:
表是由多个无符号数或者其他表作为数据项构成的复合数据类型, 所有表都习惯性地以“ _info” 结尾。 表用
于描述有层次关系的复合结构的数据, 整个Class文件本质上就是一张表
Class文件格式解析:
java代码:
class TestClass
{
private int m;
private int inc(){
return m+1;
}
}
class字节码16进制表示:
—魔数:0xCAFEBABE
每个Class文件的头4个字节称为魔数( Magic Number) , 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件
—Class文件版本:0x0000
第5和第6个字节是次版本号( Minor Version) , 第7和第8个字节是主版本号( Major Version)
—常量池:0x0013
常量池可以理解为Class文件之中的资源仓库
常量池中常量的数量是不固定的, 所以在常量池的入口需要放置一项u2类型的数据, 代表常量池容量计数值( constant_pool_count),这个容量计数是从1而不是0开始的
常量池中主要存放两大类常量:
字面量( Literal)
字面量( Literal),如文本字符串、 声明为final的常量值等。
符号引用( Symbolic References)
类和接口的全限定名( Fully Qualified Name)
字段的名称和描述符( Descriptor)
方法的名称和描述符
使用Javap命令输出常量表:
D:\java2>javap -verbose TestClass
Classfile /D:/java2/TestClass.class
Last modified 2017-12-15; size 275 bytes
MD5 checksum 73de2fede86f2f34b6780d896a538428
Compiled from "TestClass.java"
class TestClass
SourceFile: "TestClass.java"
minor version: 0
major version: 51
flags: ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // TestClass.m:I
#3 = Class #17 // TestClass
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 TestClass.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 TestClass
#18 = Utf8 java/lang/Object
{
TestClass();
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
}
—访问标志
两个字节代表访问标志( access_flags) , 这个标志用于识别一些类或者接口层次的访问信息, 包括: 这个Class是类还是接口; 是否定义为public类型; 是否定义为abstract类型; 如果是类的话, 是否被声明为final等
—类索引、 父类索引与接口索引集合
类索引( this_class) 和父类索引( super_class) 都是一个u2类型的数据, 而接口索引集合( interfaces)是一组u2类型的数据的集合, Class文件中由这三项数据来确定这个类的继承关系。 类索引用于确定这个类的全限定名, 父类索引用于确定这个类的父类的全限定名。
–字段表集合
字段表( field_info) 用于描述接口或者类中声明的变量。 字段( field) 包括类级变量以及实例级变量, 但不包括在方法内部声明的局部变量。
字段包含的的信息有: 字段的作用域( public、 private、 protected修饰符) 、 是实例变量还是类变量( static修饰符) 、 可变性( final) 、 并发可见性( volatile修饰符, 是否强制从主内存读写) 、 可否被序列化( transient修饰符) 、 字段数据类型( 基本类型、 对象、 数组) 、 字段名称。
—方法表集合
方法表的结构如同字段表一样, 依次包括了访问标志( access_flags) 、 名称索引( name_index) 、 描述符索引( descriptor_index) 、 属性表集合( attributes) 几项
—属性表集合
4、字节码指令
字节码与数据类型
加载和存储指令
运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定运算, 并把结果重新存入到操作栈顶。
类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换, 这些转换操作一般用于实现用户代码中的显式类型转换操作
对象创建与访问指令
对象创建后, 就可以通过对象访问指令获取对象实例或者数组实
例中的字段或者数组元素
操作数栈管理指令
将操作数栈的栈顶一个或两个元素出栈: pop、 pop2。
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: dup、 dup2、 dup_x1、 dup2_x1、 dup_x2、dup2_x2。
将栈最顶端的两个数值互换: swap
控制转移指令
条件分支: ifeq、 iflt、 ifle、 ifne、 ifgt、 ifge、 ifnull、 ifnonnull、 if_icmpeq、 if_icmpne、if_icmplt、 if_icmpgt、 if_icmple、 if_icmpge、 if_acmpeq和if_acmpne。
复合条件分支: tableswitch、 lookupswitch。
无条件分支: goto、 goto_w、 jsr、 jsr_w、 ret。
方法调用和返回指令
异常处理指令
同步指令
5、公有设计和私有实现
6、Class文件结构的发展