什么是字节码
说起字节码就不得不说两个东西:java语言规范和java虚拟机规范。
Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨平台的角度去设计的。
java语言的跨平台在一定程度上也是字节码带来的益处
java语言是跨平台的,所谓一次编写,到处运行。之所以是跨平台的,就是java定义了一套与操作系统,硬件无关的字节码格式,这个字节码就是用java class文件来表示的,java class文件内部定义了虚拟机可以识别的字节码格式,这个格式是平台无关性的,在linux系统或者在windows系统上都是一致的。这个就好比html文件,我们定义好规范,这个系统只要去按照规范显示出来里面的内容就好了。好比html就是class文件,浏览器就是虚拟机一样,通过浏览器去执行html的渲染过程,我们无论是用手机,Windows系统,苹果系统上网,显示出来的内容都是一样。 java虚拟机可以从class文件中加载预定义的字节码,也可以从网络,数据库,消息文件中加载字节码。
不仅仅java才有字节码
例如groovy 、Scala等等编译之后都可以转换为字节码,运行在jvm虚拟机上。
java代码被编译之后,最终输出了.class文件。这个文件就是字节码文件,会被jvm虚拟机执行。
class 文件 结构
两种数据类型:无符号数和表。
无符号数就是u1、u2、u4、u8来分别代表1个、2个、4个、8个字节。表是由
多个无符号数或其他表构成的复合数据类型,以“_info”结尾。在表开始位置,
通常会使用一个前置的容量计数器,因为表通常要描述数量不定的多个数据。
来看看,
其中上图中的第1、2、 3 行为header ,共暂用8个字节。
header中包含的版本号信息,和jdk的兼容性有关。只有高版本编译的class文件可以兼容低版本的jdk。也就是向下兼容。
实际分析
class文件结构之header
还记得上一篇文章,我们写的测试程序吗,代码如下
<pre>
public class Main {
public static void main(String args [] ) {
System.out.println(" program main method execute ! ");
}
}
</pre>
用UE打开
Header部分,前八位:00000000h: CA FE BA BE 00 00 00 31 ; 漱壕...1
前四位(U4)为CAFEBABE是魔数,紧接着的00 00 ,次版本号(U2),再接着的 00 31 代表主版本(U2) 十进制是 49 ,根据如下的jdk版本对照表,得知,最低兼容 1.5
class文件结构之常量池
常量池,是保存所有常量的一个区间,包括但不限于字符串,还包含方法名,字面量等等。当前类的类名, 字段名, 方法名, 各个字段和方法的描述符, 对当前类的字段和方法的引用信息, 当前类中对其他类的引用信息等等。可以说,它保存了当前类的所有元数据!!!
常量池中包含的数据类型如下
下一步,00 22 ,代表常量池中常量的个数,22 十进制为34 ,表示常量池里有下标为1~33的表项目。
下标从1开始而不是0,是因为第0个表项表示“不引用常量池中的任意一项”。具体表项看看下图。
这些表项有一个共同的特点是,他们的TAG都是U1类型的!!!
幸亏他们的TAG都是U1类型的,否则,jvm该如何计算和分析呢!
分析第一项:0A 为tag,十进制为10对应上方表格中的CONSTANT_Methodref_info , 这个methodref结构是 u1 / u2 / u2 ,u1代表tag刚才说了,再看第二个u2代表的index为00 06 十进制是6 ,指向方法声明类描述符class_info中的索引。第三个元素u2 代表的index是 00 14 十进制是 20,同样指向字段描述。nameAndType_info的索引。综合起来,这个0A 0006 0014 就代表了方法的描述。
其他部分的理解和分析,也都大致如此。
常量池主要为后面的_info字段服务的。
我们看看,刚才我们的那个类都有哪些常量。
逐个分析是比较麻烦的,可以使用JDK自带的用于分析Class文件字节码的工具javap
class文件结构之访问标志(access_flags)
class文件结构之 类索引、父类索引与接口索引集合(this_class 、 super_class 、 interfaces_count 和 interfaces)
- this_class:**类索引,用于确定这个类的全限定名,占2字节
- super_class:**父类索引,用于确定这个类父类的全限定名(Java语言不允许多重继承,故父类索引只有一个。除了java.lang.Object类之外所有类都有父类,故除了java.lang.Object类之外,所有类该字段值都不为0),占2字节
- interfaces_count:**接口索引计数器,占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节,
- interfaces:**接口索引集合,一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中
this_class、super_class与interfaces中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量,通过这个常量中保存的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
字段表集合(fields_count 和 fields)
fields_count:字段表计数器,即字段表集合中的字段表数据个数。占2字节
fields:字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量
在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式如下表所示:
字段修饰符放在access_flags中,占2字节
方法表集合(methods_count 和 methods)
methods_count:方法表计数器,即方法表集合中的方法表数据个数。
methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样:
数据项的含义非常相似,仅在访问标志位和属性表集合中的可选项上有略微不同
由于ACC_VOLATILE标志和ACC_TRANSIENT标志不能修饰方法,所以access_flags中不包含这两项,同时增加ACC_SYNCHRONIZED标志、ACC_NATIVE标志、ACC_STRICTFP标志和ACC_ABSTRACT标志
属性表集合(attributes_count 和 attributes)
在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息
与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性:
每种属性均有各自的表结构。这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型。
详情参考下面的链接第二条。
参考:
https://github.com/waylau/java-virtual-machine-specification
http://blog.csdn.net/a19881029/article/details/16117251