zoukankan      html  css  js  c++  java
  • 《深入理解java虚拟机》笔记——简析java类文件结构

      一直不太搞得明确jvm究竟是如何进行类载入的,在看资料的过程中迷迷糊糊。在理解类载入之前,首先看看java的类文件结构究竟是如何的,都包含了哪些内容。

      最直接的參考当然是官方文档:The Java® Virtual Machine Specification

      我写了一个最简单的java程序。依据这个程序来分析一下.class文件里究竟都存了些什么。

    java程序:

    class Par {
    
        public int x = 5;
        public void f(){
            System.out.println("par static");
        }
    }
    class Sub extends Par{
        public int x = 6;
        public void f() {
            System.out.println("Sub static");
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            Par par = new Sub();
            System.out.println(par.x);
            par.f();
        }
    }
    

    生成的.class文件内容

    cafe babe 0000 0033 0027 0a00 0900 1207
    0013 0a00 0200 1209 0014 0015 0900 1600
    170a 0018 0019 0a00 1600 1a07 001b 0700
    1c01 0006 3c69 6e69 743e 0100 0328 2956
    0100 0443 6f64 6501 000f 4c69 6e65 4e75
    6d62 6572 5461 626c 6501 0004 6d61 696e
    0100 1628 5b4c 6a61 7661 2f6c 616e 672f
    5374 7269 6e67 3b29 5601 000a 536f 7572
    6365 4669 6c65 0100 0954 6573 742e 6a61
    7661 0c00 0a00 0b01 0016 636f 6d2f 7468
    696e 6b69 6e67 696e 6a61 7661 2f53 7562
    0700 1d0c 001e 001f 0700 200c 0021 0022
    0700 230c 0024 0025 0c00 2600 0b01 0017
    636f 6d2f 7468 696e 6b69 6e67 696e 6a61
    7661 2f54 6573 7401 0010 6a61 7661 2f6c
    616e 672f 4f62 6a65 6374 0100 106a 6176
    612f 6c61 6e67 2f53 7973 7465 6d01 0003
    6f75 7401 0015 4c6a 6176 612f 696f 2f50
    7269 6e74 5374 7265 616d 3b01 0016 636f
    6d2f 7468 696e 6b69 6e67 696e 6a61 7661
    2f50 6172 0100 0178 0100 0149 0100 136a
    6176 612f 696f 2f50 7269 6e74 5374 7265
    616d 0100 0770 7269 6e74 6c6e 0100 0428
    4929 5601 0001 6600 2100 0800 0900 0000
    0000 0200 0100 0a00 0b00 0100 0c00 0000
    1d00 0100 0100 0000 052a b700 01b1 0000
    0001 000d 0000 0006 0001 0000 0018 0009
    000e 000f 0001 000c 0000 003b 0002 0002
    0000 0017 bb00 0259 b700 034c b200 042b
    b400 05b6 0006 2bb6 0007 b100 0000 0100
    0d00 0000 1200 0400 0000 1b00 0800 1c00
    1200 1d00 1600 1e00 0100 1000 0000 0200
    11

    使用javap反汇编一下生成的.class文件(javap -v com.thinkinginjava.Test)

    Classfile /E:/Workspaces/MyEclipse/websocketTest/src/com/thinkinginjava/Test.cla
    ss
      Last modified 2016-4-2; size 513 bytes
      MD5 checksum 32ca9f73cf634398be1085454c6b21c3
      Compiled from "Test.java"
    public class com.thinkinginjava.Test
      SourceFile: "Test.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #9.#18         //  java/lang/Object."<init>":()V
       #2 = Class              #19            //  com/thinkinginjava/Sub
       #3 = Methodref          #2.#18         //  com/thinkinginjava/Sub."<init>":()V
       #4 = Fieldref           #20.#21        //  java/lang/System.out:Ljava/io/PrintStream;
       #5 = Fieldref           #22.#23        //  com/thinkinginjava/Par.x:I
       #6 = Methodref          #24.#25        //  java/io/PrintStream.println:(I)V
       #7 = Methodref          #22.#26        //  com/thinkinginjava/Par.f:()V
       #8 = Class              #27            //  com/thinkinginjava/Test
       #9 = Class              #28            //  java/lang/Object
      #10 = Utf8               <init>
      #11 = Utf8               ()V
      #12 = Utf8               Code
      #13 = Utf8               LineNumberTable
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               SourceFile
      #17 = Utf8               Test.java
      #18 = NameAndType        #10:#11        //  "<init>":()V
      #19 = Utf8               com/thinkinginjava/Sub
      #20 = Class              #29            //  java/lang/System
      #21 = NameAndType        #30:#31        //  out:Ljava/io/PrintStream;
      #22 = Class              #32            //  com/thinkinginjava/Par
      #23 = NameAndType        #33:#34        //  x:I
      #24 = Class              #35            //  java/io/PrintStream
      #25 = NameAndType        #36:#37        //  println:(I)V
      #26 = NameAndType        #38:#11        //  f:()V
      #27 = Utf8               com/thinkinginjava/Test
      #28 = Utf8               java/lang/Object
      #29 = Utf8               java/lang/System
      #30 = Utf8               out
      #31 = Utf8               Ljava/io/PrintStream;
      #32 = Utf8               com/thinkinginjava/Par
      #33 = Utf8               x
      #34 = Utf8               I
      #35 = Utf8               java/io/PrintStream
      #36 = Utf8               println
      #37 = Utf8               (I)V
      #38 = Utf8               f
    {
      public com.thinkinginjava.Test();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 24: 0
    
      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #2                  // class com/thinkinginjava/Sub
             3: dup
             4: invokespecial #3                  // Method com/thinkinginjava/Sub."<init>":()V
             7: astore_1
             8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            11: aload_1
            12: getfield      #5                  // Field com/thinkinginjava/Par.x:I
            15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
            18: aload_1
            19: invokevirtual #7                  // Method com/thinkinginjava/Par.f:()V
            22: return
          LineNumberTable:
            line 27: 0
            line 28: 8
            line 29: 18
            line 30: 22
    }

      依据java虚拟机规范,首先介绍一下Class 文件格式

      “Class文件格式採用一种相似于C语言结构体的伪结构来存储数据,这样的伪结构仅仅有两种数据类型:无符号数和表。


      无符号数属于主要的数据结构,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数;
      表是由多个无符号数或者其它表作为数据项构成的复合数据类型。表都习惯性的以“_info”结尾。

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

    一个个来分析:

    魔数、主次版本号号

    这里写图片描写叙述

    我本地的是JDK 1.7.0的,所以是主次版本号号是 00 00 00 33,不同的jdk版本号生成的版本号号不同。

    常量池

      在主次版本号号后面的就是常量池入口,由于常量池的数量是不固定的。所以在常量池的入口须要放置一项u2类型的数据,代表常量池容量计数值
    这里写图片描写叙述

      可见当前的计数值为0x0027 ,即39。这代表常量池中有38项常量。索引號为1-38,Class文件仅仅有常量池的容器技术是从1開始的
      从之前的反汇编代码中也能够看到,常量池中确实有38项
      
    这里写图片描写叙述

      常量池中主要有3类常量:

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

      常量池中的项目类型例如以下:
      这里写图片描写叙述

    每一种常量都有它的结构。比如:

    CONSTANT_Class_info {
        u1 tag; //标志。占1个字节 07
        u2 name_index;//指向全限定名常量项的索引
    }
    CONSTANT_Utf8_info {
        u1 tag;//标志 01
        u2 length;//UTF-8编码的字符串占用的字节数
        u1 bytes[length];//长度为length的UTF-8编码的字符串
    }
    。。。

    。每一项可详细參考官方文档

    举几个样例:

    以#1为例, #1 = Methodref #9.#18
    The CONSTANT_MethodHandle_info Structure

    CONSTANT_MethodHandle_info {
        u1 tag; //标志 10
        u1 reference_kind; //指向声明方法的类描写叙述符的CONSTANT_Class_info的索引项
        u2 reference_index;//指向名称及类型描写叙述符CONSTANT_NameAndType的索引项
    }
    

    tag:0a
    reference_kind:0x0009
    reference_index:0x0012
    这里写图片描写叙述

    以#10为例 #10 = Utf8 < init >
    The CONSTANT_Utf8_info Structure

    CONSTANT_Utf8_info {
            u1 tag;//标志 01
            u2 length;//UTF-8编码的字符串占用的字节数
            u1 bytes[length];//长度为length的UTF-8编码的字符串
        }
    

    tag: 01
    length: 6
    bytes[length]:详细相应的编码 3c 69 6e 69 74 3e
    这里写图片描写叙述

    能够看到。常量区总共同拥有38项。在.class文件里的表演示样例如以下图所看到的。依照上面的分析就可以分析出每一个常量相应的是哪些16进制位
    这里写图片描写叙述

    訪问标记

      分析完常量池,在常量池结束后。紧跟着的两个字节代表訪问标志。这个标志用于识别一些类或者接口层次的訪问信息。


      (直接截图了。。


    这里写图片描写叙述

      从代码中能够分析出。我写的java程序的訪问标志位0x0021。即ACC_PUBLIC、ACC_SUPER标志位真。其余标志为假,因此access_flags的值为0x0001 | 0x0020 = 0x0021(或运算就可以算出是哪几个标志为真,哪几个为假),例如以下图所看到的。

    这里写图片描写叙述

    类索引、父类索引与接口索引集合

      类索引、父类索引和接口索引集合都按顺序排列在訪问标记之后。

      类索引(this_class)和父类索引(super_class)都是一个u2类型的数据。接口索引集合是一组u2类型的集合。

    • 类索引用于确定这个类的全限定名
    • 父类索引用于确定这个类的父类的全限定名

       对于接口索引集合,入口第一项u2类型的数据为接口计数器(interface_count),表示索引表的容量。,没有实现不论什么接口。则计数为0,后面接口的索引表不再占用不论什么字节。

       例如以下图所看到的,类索引相应0x0008,即com/thinkinginjava/Test类。父类索引为Object类。0x0009即指向java/lang/Object

    这里写图片描写叙述

    字段表集合

      字段表用于标书接口或者类中声明的变量。

    字段包含类级变量以及实例级变量,但不包含在方法内部声明的局部变量。


      
      java中描写叙述一个字段包含的信息有:

    • 字段的作用域(public,protected,private)
    • 实例变量还是类变量(static)
    • 可变性(final)
    • 并发可见性(volatile)
    • 可否序列化(transient)
    • 字段数据类型(基本类型。对象。数组)
    • 字段名称

      关于字段表能够參考这篇博文看看
      由于我的程序中Test类中未定义字段,所以结果显示为0x0000,例如以下图

      这里写图片描写叙述

    方法表集合

      紧接着字段表的是方法表集合,理解了字段表看方法表就相对简单了
      方法表的结构与字段表一样。依次包含了訪问标志(access_flags)、名称索引(name_index)、描写叙述符索引(descriptor_index)、属性表集合(attributes)几项
    官方文档

    method_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    方法訪问标志:
    这里写图片描写叙述

    分析一下字节码:

    • 方法表集合的计数器值为0x0002,代表集合中有两个方法(一个是编译去加入的实例构造器<init>和源代码中的main方法)
    • 第一个方法的訪问标志为0x0001,即仅仅有ACC_PUBLIC标志为真
    • 名称索引值为0x000a,查找常量池可得方法名为<init>
    • 描写叙述符索引值为0x000b,相应常量为”()V”
    • 属性表计数器值为0x0001。表示有一个属性。相应的属性名称索引为0x000c,相应常量为“Code”。说明此属性时方法的字节码描写叙述。

    这里写图片描写叙述

    属性表集合

    官方文档
      属性表在之前出现过多次了,在Class文件、字段表、方法表中都能够携带自己的属性表集合。用于描写叙述某些场景专有的信息。


      对于每一个属性,它的名称须要从一个常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是全然自己定义的,仅仅须要通过一个u4的长度属性去说明属性值所占用的位数就可以。

    属性表结构例如以下所看到的:

    attribute_info {
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }
    

    举几个样例:
    1.Code属性
    java方法体中的代码经过javac编译处理后,终于会变成字节码指令存储在Code属性内。Code属性出如今方法表的属性集合之中,但并不是全部的方法表都必须存在这个属性。譬如接口或者抽象类中的方法就不存在Code属性。假设Code属性存在,那么它的结构例如以下所看到的:

    Code_attribute {
        u2 attribute_name_index; //指向CONSTANT_Utf8_info型常量的索引
        u4 attribute_length;     //属性值的长度
        u2 max_stack;            //操作数栈深度的最大值
        u2 max_locals;           //局部变量表所需的存储空间
        //code_length和code用来存储java源程序编译后生成的字节码指令
        u4 code_length;          //字节码长度
        u1 code[code_length];    //存储字节码指令的一系列字节流
        u2 exception_table_length;
        {   u2 start_pc;
            u2 end_pc;
            u2 handler_pc;
            u2 catch_type;
        } exception_table[exception_table_length];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    分析一下字节码:

    • 由上面方法表的分析,第一个<init>方法相应的属性名为Code,其值为0x000c
    • 紧接着为attribute_length,表示属性值的长度,其值为0x0000001d
    • 接着为max_stack。表示操作数栈深度的最大值,其值为0x0001
    • 接着为max_locals, 表示局部变量表所需的存储空间,其值为0x0001
    • 接着为code_length。占4个字节。表示字节码长度,其值为0x00000005
    • 紧接着读入尾随的5个字节,依据字节码指令表翻译出相应的字节码指令。翻译“2a b7 00 01 b1”过程:
      1)读入2a。对用指令aload_0
      2)读入b7,相应指令invokespecial
      3)读入00 01,这是invokespecial的參数,为常量池中0x0001相应的常量
      4)读入b1,相应的指令为return

    这里写图片描写叙述

    • 接着为exception_table_length,此处为0,没有异常
    • 接着为attribute_count。代表这个Code中有1个属性
    • 然后就是这个属性的描写叙述啦,索引號为0x000d,找到相应的常量池索引號。为LineNumberTable,so,这个属性是LineNumberTable。那么看一下LineNumberTable属性结构:
      LineNumberTable属性

       LineNumberTable_attribute {
          u2 attribute_name_index;            //属性名索引
          u4 attribute_length;                //属性长度
          u2 line_number_table_length;
          {   u2 start_pc;                 //字节码行号
              u2 line_number;              //java源代码行号
          } line_number_table[line_number_table_length];
      }
      

      从class文件里能够看出,
      attribute_name_index为0x000d,
      attribute_length为0x00000006。
      line_number_table_length为0x0001。
      start_pc为0x0001
      line_number为0x0018
      正好相应了

        LineNumberTable:
          line 24: 0
      

      这里写图片描写叙述

      到此为止,一个<init>方法分析完了。包含了它包含的属性。


      之前说了。该程序共同拥有2个方法。另一个为main函数。对它的分析跟上面一样,不做分析了

    这里写图片描写叙述

    有些博客也能够參考一下,写的比較细
    http://blog.csdn.net/u010349169/article/category/2620885

  • 相关阅读:
    PEM_密钥对生成与读取方法
    RandomStringUtils RandomUtils
    Java高级个人笔记(RandomStringUtils工具类)
    如何让eclipse输出结果的console栏自动换行?
    日志框架logj的使用
    IDEA中Git的更新、提交、还原方法
    AWS核心服务概览
    技术资料的几种读法
    李笑来老师在《把时间当作朋友》曾说过:“所有学习上的成功,都只靠两件事:策略和坚持,而坚持本身就应该是最重要的策略之一
    ubuntu如何修改terminal终端的主机名(修改/etc/hostname文件)
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7235724.html
Copyright © 2011-2022 走看看