您的位置:首页 > 其它

Class字节码内容学习

2015-12-13 10:24 204 查看
今天看了一下class文件的字节码内容,做个记录。先看下java的文件:

class TestClass{
private int m;
public static int lala = 2;
public int inc(){
return m+1;
}

}
class SubClass extends TestClass{
private int b=0;
private TestClass sub= null;
public int test(){
int cc = 0;
return cc;
}

}

将此文件javac编译,得到两个class文件,这里说下要用的工具:winhex十六进制工具 以及javap

简单的说下class文件结构。


class文件的前四位是固定的,称为魔数,CAFEBABE(咖啡宝贝),后面3位是主版本号,主版本号之后两位是次版本号。下一位是常量池容量。然后后面很长的内容都是常量池数据,常量池之后是code字节码数据。我们就不看十六进制数据了,直接看下javap为我们计算得到的class信息。

Classfile /C:/Users/nys/Desktop/TestClass.class

Last modified 2015-12-13; size 354 bytes

MD5 checksum fc317f27d60bc2718a828e8834854fe2

Compiled from "TestClass.java"

class TestClass

minor version: 0

major version: 52

flags: ACC_SUPER

Constant pool:

#1 = Methodref #5.#18 // java/lang/Object."<init>":()V

#2 = Fieldref #4.#19 // TestClass.m:I

#3 = Fieldref #4.#20 // TestClass.lala:I

#4 = Class #21 // TestClass

#5 = Class #22 // java/lang/Object

#6 = Utf8 m

#7 = Utf8 I

#8 = Utf8 lala

#9 = Utf8 <init>

#10 = Utf8 ()V

#11 = Utf8 Code

#12 = Utf8 LineNumberTable

#13 = Utf8 inc

#14 = Utf8 ()I

#15 = Utf8 <clinit>

#16 = Utf8 SourceFile

#17 = Utf8 TestClass.java

#18 = NameAndType #9:#10 // "<init>":()V

#19 = NameAndType #6:#7 // m:I

#20 = NameAndType #8:#7 // lala:I

#21 = Utf8 TestClass

#22 = Utf8 java/lang/Object

{

public static int lala;

descriptor: I

flags: ACC_PUBLIC, ACC_STATIC

TestClass();

descriptor: ()V

flags:

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: return

LineNumberTable:

line 1: 0

public int inc();

descriptor: ()I

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: getfield #2 // Field m:I

4: iconst_1

5: iadd

6: ireturn

LineNumberTable:

line 5: 0

static {};

descriptor: ()V

flags: ACC_STATIC

Code:

stack=1, locals=0, args_size=0

0: iconst_2

1: putstatic #3 // Field lala:I

4: return

LineNumberTable:

line 3: 0

}

SourceFile: "TestClass.java"
这是TestClass的class文件,我们看到主次版本号之后,有一个constant pool常量池,看下常量池内容。可以看到常量池的第一项是方法表(class文件由两部分组成,一是无回复无符号数,u1 u2 u4 u8,二是表,表是由多个无符号数和其它表构成的数据项,class文件本身就是一张表结构),指向偏移量#5.#18,我们往下可以看到#5是一个class表,指向#22,而#22就是java/lang/object,继续看下#18,可以看到是init<>f方法,所以我们可知,第一个methodref是object的init<>方法,后面的以此类推。下面是SubClass的文件内容

class SubClass extends TestClass

minor version: 0

major version: 52

flags: ACC_SUPER

Constant pool:

#1 = Methodref #5.#18 // TestClass."<init>":()V

#2 = Fieldref #4.#19 // SubClass.b:I

#3 = Fieldref #4.#20 // SubClass.sub:LSubClass;

#4 = Class #21 // SubClass

#5 = Class #22 // TestClass

#6 = Utf8 b

#7 = Utf8 I

#8 = Utf8 sub

#9 = Utf8 LSubClass;

#10 = Utf8 <init>

#11 = Utf8 ()V

#12 = Utf8 Code

#13 = Utf8 LineNumberTable

#14 = Utf8 test

#15 = Utf8 ()I

#16 = Utf8 SourceFile

#17 = Utf8 TestClass.java

#18 = NameAndType #10:#11 // "<init>":()V

#19 = NameAndType #6:#7 // b:I

#20 = NameAndType #8:#9 // sub:LSubClass;

#21 = Utf8 SubClass

#22 = Utf8 TestClass

{

SubClass();

descriptor: ()V

flags:

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method TestClass."<init>":()V

4: aload_0

5: iconst_0

6: putfield #2 // Field b:I

9: aload_0

10: aconst_null

11: putfield #3 // Field sub:LSubClass;

14: return

LineNumberTable:

line 9: 0

line 10: 4

line 11: 9

public int test();

descriptor: ()I

flags: ACC_PUBLIC

Code:

stack=1, locals=2, args_size=1

0: iconst_0

1: istore_1

2: iload_1

3: ireturn

LineNumberTable:

line 13: 0

line 14: 2

}

SourceFile: "TestClass.java"

对比一下,第一个为MethodRef,指向初始化int方法,后面是类自身的Fieldref,再往后面就是一个class表,偏移量是指向的自身class的描述表信息,后面紧跟一个class表,指向其父类。同时可以看到父类中private int m;以及public static int lala = 2;,在子类的常量池中并没有相应的Filedref表,由此可知,子类继承父类的时候,其父类的method和filed是不会写入子类的常量池的。我们修改一下SubClass的内容。
class SubClass extends TestClass {

private int b = 0;

public TestClass sub = new SubClass();

public int test() {
System.out.println("b");
int cc = 0;
return cc;
}

public void println() {
Map map = new HashMap();
map.put("","");
sub.test();
}

}

然后编译,看下class文件内容:
<div>Classfile /C:/Users/nys/Desktop/SubClass.class
  Last modified 2015-12-13; size 698 bytes
  MD5 checksum bac3236b7989742ba907bf359a9ac30b
  Compiled from "TestClass.java"
class SubClass extends TestClass
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#28        // TestClass."<init>":()V
   #2 = Fieldref           #3.#29         // SubClass.b:I
   #3 = Class              #30            // SubClass
   #4 = Methodref          #3.#28         // SubClass."<init>":()V
   #5 = Fieldref           #3.#31         // SubClass.sub:LTestClass;
   #6 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = String             #15            // b
   #8 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #9 = Class              #36            // java/util/HashMap
  #10 = Methodref          #9.#28         // java/util/HashMap."<init>":()V
  #11 = String             #37            //
  #12 = InterfaceMethodref #38.#39        // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #13 = Methodref          #14.#40        // TestClass.test:()I
  #14 = Class              #41            // TestClass
  #15 = Utf8               b
  #16 = Utf8               I
  #17 = Utf8               sub
  #18 = Utf8               LTestClass;
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               test
  #24 = Utf8               ()I
  #25 = Utf8               println
  #26 = Utf8               SourceFile
  #27 = Utf8               TestClass.java
  #28 = NameAndType        #19:#20        // "<init>":()V
  #29 = NameAndType        #15:#16        // b:I
  #30 = Utf8               SubClass
  #31 = NameAndType        #17:#18        // sub:LTestClass;
  #32 = Class              #42            // java/lang/System
  #33 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #34 = Class              #45            // java/io/PrintStream
  #35 = NameAndType        #25:#46        // println:(Ljava/lang/String;)V
  #36 = Utf8               java/util/HashMap
  #37 = Utf8
  #38 = Class              #47            // java/util/Map
  #39 = NameAndType        #48:#49        // put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #40 = NameAndType        #23:#24        // test:()I
  #41 = Utf8               TestClass
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               java/io/PrintStream
  #46 = Utf8               (Ljava/lang/String;)V
  #47 = Utf8               java/util/Map
  #48 = Utf8               put
  #49 = Utf8               (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
{
  public TestClass sub;
    descriptor: LTestClass;
    flags: ACC_PUBLIC</div><div>
</div><div>  SubClass();
    descriptor: ()V
    flags:
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method TestClass."<init>":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field b:I
         9: aload_0
        10: new           #3                  // class SubClass
        13: dup
        14: invokespecial #4                  // Method "<init>":()V
        17: putfield      #5                  // Field sub:LTestClass;
        20: return
      LineNumberTable:
        line 20: 0
        line 22: 4
        line 24: 9</div><div>
</div><div>  public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String b
         5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: iconst_0
         9: istore_1
        10: iload_1
        11: ireturn
      LineNumberTable:
        line 27: 0
        line 28: 8
        line 29: 10</div><div>
</div><div>  public void println();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #9                  // class java/util/HashMap
         3: dup
         4: invokespecial #10                 // Method java/util/HashMap."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #11                 // String
        11: ldc           #11                 // String
        13: invokeinterface #12,  3           // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        18: pop
        19: aload_0
        20: getfield      #5                  // Field sub:LTestClass;
        23: invokevirtual #13                 // Method TestClass.test:()I
        26: pop
        27: return
      LineNumberTable:
        line 33: 0
        line 34: 8
        line 37: 19
        line 38: 27
}
SourceFile: "TestClass.java"</div>


可以看到,我们在方法里new的HashMap。会在常量池里有所体现,
#9 = Class #34 // java/util/HashMap

#10 = Methodref #9.#26 // java/util/HashMap."<init>":()V
#12 = InterfaceMethodref #38.#39 // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
由此可知,在编译生成class文件时,也会将方法内部的类方法调用等信息放在常量池中形成表数据,在运行时确定直接引用(符号引用-->直接引用)。
我们看下code字节码的内容,注意看下map.put("","");这几生成的指令,如下:
13: invokeinterface #12, 3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
可以看到,这里调用的是invokeinterface指令,这里就是动态链接,运行时确定具体内存地址的方法。
另外,虚拟机的加载存储指令,用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,load型号指令用于局部变量加载到操作数栈,store指令用于将数据从操作数栈存储到局部变量表。
注:locals参数是局部变量表的最大容量,单位Slot。局部变量表位于栈帧内,大小在编译器就已确定(比如locals参数),而栈帧中存放了局部变量表 操作数栈 动态连接 返回地址等信息。Slot中可存放 boolean byte char short int float reference 和returnAddress8种类型。其中reference是引用类型,此引用可以直接或间接的找到的内存地址,以及对象在方法区的class数据。局部变量表对Slot使索引定位的方式 进行管理,下表从0开始,0为存储this引用,剩下Slot按照形参顺序,从一开始进行分配,形参分配Slot后,在对方法内部参数进行Slot分配。Slot是可重用对。其字节大小并没有
明确对说明,可以存放32位以数据,而龙double需要使用两个Slot.Slot的复用回对gc产生影响,如果一个方法后面执行对时间较长,而前面又new 了大量对内存空间,此时这些内存不会被回收(如果这些Slot被复用了,那么堆区对内存就可回收,引用已不存在)。所以在不确定Slot是否会被后面变量复用对时候,最好手动置为null。
每个栈帧都包含一个指向运行时常量池中该栈帧锁属方法对引用,持有这个引用是为了支持方法调用过程中对动态链接。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: