Class字节码内容学习

今天看了一下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。
每个栈帧都包含一个指向运行时常量池中该栈帧锁属方法对引用,持有这个引用是为了支持方法调用过程中对动态链接。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值