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];
}

  1. magic:
    魔数,确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE。

  2. minor_version;
    class文件的副版本号

  3. major_version;
    class文件的主版本号。主,副版本号决定Class文件的版本号。不同版本的虚拟机实现支持的版本号也不同,高版本号的 虚拟机实现可以支持低版本号的 Class 文件

  4. constant_pool_count;
    常量池计数器,从1开始计数,constant_pool_count 的值等于 constant_pool 表中的成员数加 1,当索引值为0时代表不引用任何一个常量池项目。

  5. cp_info constant_pool[constant_pool_count-1];
    常量池,constant_pool 是一种表结构,主要存放字面量和引用量,可以用来表示常量引用量的有

    类和接口的全限定名
    字段的名称和描述符
    方法的名称和描述符

  6. access_flags;
    访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性

  7. this_class
    类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量

  8. super_class;
    父类索引,对于类来说,super_class 的值必须为 0 或者是对constant_pool表中项目的一个有效索引值。

  9. interfaces_count;
    接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。

  10. interfaces[interfaces_count];
    接口表,类实现的接口

  11. fields_count;
    字段计数器,fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。

  12. field_info fields[fields_count];
    字段表,fields[]数组中的每个成员都用于表示当前类或接口中某个字段的完整描述。

  13. methods_count;
    方法计数器,methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。

  14. method_info methods[methods_count];
    方法表,methods[]数组中的每个成员都用于表示当前类或接口中某个方法的完整描述。methods[]数组
    只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

  15. attributes_count;
    属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。

  16. 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虚拟机

posted on 2015-01-13 00:52  好好先生耶  阅读(132)  评论(0)    收藏  举报