首页 > 代码库 > class文件概述
class文件概述
将java代码编译后会产生class文件,并且一个clas文件会对应唯一一个java类或者接口。下面对一个通过一个简单的例子来简述一下class文件的结构。
java代码
public class JavaMethodAreaOOM{ String str = "abc"; int i = 1; static String str1 = "123"; public static void main(String[] args) { int b = 1; } public void test(){ System.out.println(str1); }}
在java代码中我们定义了一些属性和方法。下面是编译生成的字节码文件
字节码文件
我们看到,源代码不长,但是生成的字节码文件内容却很多,下面就针对上面的例子进行分析。
字节码分析
在字节码文件中只存储了两种类型的数据,一种是无符号数(u1,u2,u4,u8,u1表示一个字节,u2表示两个字节...),一种是表(表其实就是无符号的集合,不过表中的数据之间是有联系的,而且表中还可以存放表,表一般以_info结尾)。 字节码文件中数据存放顺序是按照下面表中的顺序存放的,比如文件的开始位置会存放4个字节的数据,我们称之为magic。所以,在进行字节码分析时,我们可以参考下面的表进行对比分析。
magic(u4)的翻译是魔术,但是这里是魔数。这个魔数的作用就是文件标识,用来标识这是一个字节码文件可以被JVM加载运行。查看字节码开始的4个字节,我们会发现是cafebabe。所有的字节码文件开始都是这样的。
minor_version(u2)紧跟着魔数有两个字节(0000)表示次版本号,次版本一般是JDK的某个分支如JDK 1.1有个分支为JDK1.1.8,那么此版本号就为0x0003。这个次版本号不太重要。
major_version(u2)接下来的两个字节(0033)表示主版本号,这个主版本号很重要。我们经常会看到用低版本的java编译器编译出来的class文件无法在高版本的虚拟机上运行就是因为JVM会查看这个值。0x0033的十进制是51表示这个class文件只能在JDK1.7以上的虚拟机中运行(版本号对应的JDK这里可自行百度)。
constant_pool_count(u2):记录常量池中常量的个数,002C的十进制为44,表示在常量池中有44个常量。因为常量的池的大小不固定,如果没有这个数的话JVM在加载常量池不知道需要申请多大的空间才合适。
constant_pool(cp_info):这就是我们所谓的常量池,我们看到常量池使用表来表示的。其原因就是常量池中存放的大量的数据,我们用最大的基本类型u8也只能表示8个字节,所以只能用表来描述这段区域了。下面就是常量池中能放的内容:字面量和符号引用。字面量就是我们定义的字符串,final常量等,而符号引用包括类和接口的全限定名,方法名和描述符,字段名和描述符。
对常量池中存放的常量都是以表的方式来描述的。在每个表的开始位置是个tag(u1),用来表示该常量是什么。我们可以对应下面的表来分析常量池中的数据。
我们开始对常量池进行分析
第一个常量0a00 0a00 1a,我们查表发现。OA是一个表示常量池中第一个常量是方法的符号引用,紧跟着我们看到000a 和001a。通过查表我们发现这是两个索引。第一个索引指向的是一个CONSTANT_Class_info这种表的数据位置,第二个索引指向CONSTANT_NameAndTyep_info这种表数据位置。简单来说,000A指向常量池中第10个常量,而001a指向第26个常量。下面是第26个常量中放的内容。我们查表可以得到07表示一个对类描述的表,0025又是一个索引,这个索引指向第37个常量。
第二个常量08 001b,通过查表(08)我们发现,第二个常量是字符串类型。他的第二个索引是001b(27),指向第27个常量。至于第27个常量中具体放的什么,这里就不做分析了。
总结一下,我们观察了常量池中的前两个常量,第一个是方法,第二个是字符串。但是我们发现方法处并未显示有具体的方法,字符串处并未看到具体的字符,放到全是索引。这就是class文件的一个特点,先说有这个东西,具体是是什么请通过后面的索引去查找。下面我们借助java中的工具看看常量池中内容。
通过工具我们可以直观的看到看到常量池中内容了。
我们看到第一个常量是方法,这和我们分析的一样。常量中的数据是索引#10和#26。在#10处我们看到是一个Class常量,而Class中有个索引是#37.#37处的常量这里没有显示,但是通过后面的注释可以看到#37处放的是java/lang/Object字符串,存储类型是UTF8,表示类名。通过一步步查找我们可以知道,方法中的第一个常量是类名,表示这个方法属于哪个类,而“<init>”表明该方法的的名称,()V表示该方法的无参且返回值为void。那这个方法具体对应代码中的那个方法呢?其实就是我们从父类继承来的无参构造器。
第二个常量是String类型,存储的数据是索引#27。而第27个常量处放的是Utf8类型,而内容为abc。这样就和我们的代码对应起来了,但是还一点就是这个字符串常量是被赋值给一个str属性的,这个str在哪里呢?
第三个常量是Fieldref,我们看到它里面存的还是索引,而索引中的内容是UTF8类型的字符串。字符串的内容是 JavaMethodAreaOOM和str:Ljava/lang/String;这表示这个属性属于JavaMethodAreaOOM类的属性,而这个属性的类型是String类型。
第四个常量也是Fieldref,这就是我代码中的int类型了。索引中存储的是JavaMethodAreaOOM和i:I,前者表示所属类,后者是名字和类型。i就是变量名而I就是int类型了。
常量池总结
量池中大的分类就是字面的量和符号引用。细化后就是字符串常量,索引值,类名,方法名,属性名,属性的类型,方法的参数,方法的返回值。而方法体以及一些其他属性的初始化操作(int=1)会放在class文件的代码中进行执行。
常量池之后是访问标志(两个字节)了,这个访问标志了这个类的访问信息。包括是接口还是类,是否为public。下面是对访问标志的描述。
class文件概述