Class类文件结构
Java是一种平台无关的语言,实现平台无关的主要原因是Java使用JVM运行,并且使用统一的程序存储格式——字节码文件。而且其他JVM语言比如Jython,JRuby通过编译为字节码文件,也可以在JVM上运行。
每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和64位长度的数据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。多字节数据项总是按照Big-Endian的顺序进行存储。class文件没有任何分隔符号,class文件各项的含义被严格约定。一个class文件格式如下:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
-
magic:
魔数,确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE。 -
minor_version;
class文件的副版本号 -
major_version;
class文件的主版本号。主,副版本号决定Class文件的版本号。不同版本的虚拟机实现支持的版本号也不同,高版本号的 虚拟机实现可以支持低版本号的 Class 文件 -
constant_pool_count;
常量池计数器,从1开始计数,constant_pool_count 的值等于 constant_pool 表中的成员数加 1,当索引值为0时代表不引用任何一个常量池项目。 -
cp_info constant_pool[constant_pool_count-1];
常量池,constant_pool 是一种表结构,主要存放字面量和引用量,可以用来表示常量引用量的有类和接口的全限定名
字段的名称和描述符
方法的名称和描述符 -
access_flags;
访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性 -
this_class
类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量 -
super_class;
父类索引,对于类来说,super_class 的值必须为 0 或者是对constant_pool表中项目的一个有效索引值。 -
interfaces_count;
接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。 -
interfaces[interfaces_count];
接口表,类实现的接口 -
fields_count;
字段计数器,fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。 -
field_info fields[fields_count];
字段表,fields[]数组中的每个成员都用于表示当前类或接口中某个字段的完整描述。 -
methods_count;
方法计数器,methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。 -
method_info methods[methods_count];
方法表,methods[]数组中的每个成员都用于表示当前类或接口中某个方法的完整描述。methods[]数组
只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。 -
attributes_count;
属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。 -
attribute_info attributes[attributes_count];
属性表,属性表的值表示描述特定场景的信息,比如方法表中的Code,或者是字段表的ConstantValue。
关于属性表
使用javap -v -p -c Test.class输出Test.class的字节码文件:
package com.lrf.yy;
public class Test {
public static void main(String[] args) {
System.out.println("hello,world");
}
}
Classfile /H:/Test.class
Last modified 2015-1-12; size 537 bytes
MD5 checksum fa822f473afb430033c9fe757212f0d7
Compiled from "Test.java"
public class com.lrf.yy.Test
SourceFile: "Test.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/lrf/yy/Test
#2 = Utf8 com/lrf/yy/Test
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/lrf/yy/Test;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Fieldref #17.#19 // java/lang/System.out:Ljava/io/PrintStream; 字段的符号引用
#17 = Class #18 // java/lang/System
#18 = Utf8 java/lang/System
#19 = NameAndType #20:#21 // out:Ljava/io/PrintStream;
#20 = Utf8 out
#21 = Utf8 Ljava/io/PrintStream;
#22 = String #23 // hello,world
#23 = Utf8 hello,world
#24 = Methodref #25.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V 类中方法的符号引用
#25 = Class #26 // java/io/PrintStream
#26 = Utf8 java/io/PrintStream
#27 = NameAndType #28:#29 // println:(Ljava/lang/String;)V 字段或方法的部分引用
#28 = Utf8 println
#29 = Utf8 (Ljava/lang/String;)V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 SourceFile
#33 = Utf8 Test.java
{
public com.lrf.yy.Test();
flags: ACC_PUBLIC //访问修饰符
Code: //方法的Code属性 ,对于实例方法参数列表最少为1,用于传递this引用
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable: //显示源码文件行号与字节码文件行号的对应关系
line 4: 0
LocalVariableTable://局部变量表
Start Length Slot Name Signature
0 5 0 this Lcom/lrf/yy/Test;
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22 // String hello,world
5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V//对于方法的参数JVM对于不同类型有不同符号表示,最后的字符代表返回类型。其中L代表对象类型,其他几种基本类型分别使用首字母大写 ;V代表void
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String; // args [] 数组使用[表示在,这里代表String[]
}
在上面的字节码文件中,首先是Constant pool 常量池,通过上面分析可知如果不知道父类会默认继承Object,并且在执行构造方法时,同时会执行父类的构造方法。。下面是一个循环语句的字节码分析:
public static void main(String[] args) {
for(int i = 0;i<10;i++){
System.out.println(i);
}
上面的一个循环输出的字节码如下:
public static void main(java.lang.String[]);
Code:
0: iconst_0 //将i赋值为0
1: istore_1 // 存储一个局部变量(i=0)
2: goto 15 //先判断是否符合条件,跳转到15行
5: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
8: iload_1
9: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
12: iinc 1, 1 //加1
15: iload_1 //将局部变量(i)入栈 ,1是变量表的序号
16: bipush 10//将常量10入栈
18: if_icmplt 5 //比较大小,如果小的话,跳到 5
21: return
}
本文参考:深入理解Java虚拟机
浙公网安备 33010602011771号