深入理解class字节码中的<init>,<clinit>

        java中有两种特殊的方法<init>,<clinit> 

1,当java类中,存在用static修饰的静态类型字段,或static块,编译器便会生成<clinit>

2,当java类中定义了构造方法,或其他非static类成员变量被赋了初始值,编译器便会生成<init>

代码

public class Test{

     private Integer i=3;

     private static int a=90;

     public void add(int a,int b){

     Test test=this;

     int z=a+b;

     int x=3;

}

     public static void main(String[] args){

       Test test=new Test();

      test.add(2,3);

    }

}

 javac Test.java

 javap -verbose Test.class 如下图所示

 java成员变量,静态属性,都在常量池中

Classfile /Users/robin/Test.class

  Last modified 2018-11-16; size 570 bytes

  MD5 checksum 179494093ddd1f13d97f138f5162f8a8

  Compiled from "Test.java"

public class Test

  minor version: 0

  major version: 52

  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

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

   #2 = Methodref          #25.#26        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

   #3 = Fieldref           #4.#27         // Test.i:Ljava/lang/Integer; //成员变量引用

   #4 = Class              #28            // Test

   #5 = Methodref          #4.#24    // Test."<init>":()V

   #6 = Methodref          #4.#29         // Test.add:(II)V

   #7 = Fieldref           #4.#30         // Test.a:I      

   #8 = Class              #31            // java/lang/Object

   #9 = Utf8               i

  #10 = Utf8               Ljava/lang/Integer;

  #11 = Utf8               a

  #12 = Utf8               I

  #13 = Utf8               <init>

  #14 = Utf8               ()V

  #15 = Utf8               Code

  #16 = Utf8               LineNumberTable

  #17 = Utf8               add

  #18 = Utf8               (II)V

  #19 = Utf8               main

  #20 = Utf8               ([Ljava/lang/String;)V

  #21 = Utf8               <clinit>

  #22 = Utf8               SourceFile

  #23 = Utf8               Test.java

  #24 = NameAndType        #13:#14        // "<init>":()V

  #25 = Class              #32            // java/lang/Integer

  #26 = NameAndType        #33:#34        // valueOf:(I)Ljava/lang/Integer;

  #27 = NameAndType        #9:#10         // i:Ljava/lang/Integer;

  #28 = Utf8               Test

  #29 = NameAndType        #17:#18        // add:(II)V

  #30 = NameAndType        #11:#12        // a:I 

  #31 = Utf8               java/lang/Object

  #32 = Utf8               java/lang/Integer

  #33 = Utf8               valueOf

  #34 = Utf8               (I)Ljava/lang/Integer;

{

  public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=1, args_size=1

         0: aload_0           //加载变量表中的this 入栈

         1: invokespecial #1  //调用常量池1位置父类的init方法  // Method java/lang/Object."<init>":()V   

         4: aload_0           //加载变量表中的this 入栈  

         5: iconst_3          //加载3入栈   

         6: invokestatic  #2  //调用常量池2位置的静态方法     //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3  //放到常量池3位置  // Field i:Ljava/lang/Integer;

        12: return

  public void add(int, int);

    descriptor: (II)V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=6, args_size=3

         0: aload_0

         1: astore_3

         2: iload_1

         3: iload_2

         4: iadd

         5: istore        4

         7: iconst_3

         8: istore        5

        10: return

      LineNumberTable:

        line 7: 0

        line 8: 2

        line 9: 7

        line 10: 10

  public static void main(java.lang.String[]);

    descriptor: ([Ljava/lang/String;)V

    flags: ACC_PUBLIC, ACC_STATIC

    Code:

      stack=3, locals=2, args_size=1

         0: new           #4                  // class Test

         3: dup

         4: invokespecial #5                  // Method "<init>":()V

         7: astore_1

         8: aload_1

         9: iconst_2

        10: iconst_3

        11: invokevirtual #6                  // Method add:(II)V

        14: return

      LineNumberTable:

        line 13: 0

        line 14: 8

        line 15: 14

  static {};

    descriptor: ()V

    flags: ACC_STATIC

    Code:

      stack=1, locals=0, args_size=0

         0: bipush        90

         2: putstatic     #7                  // Field a:I

         5: return

      LineNumberTable:

        line 4: 0

}

jdb命令打断点调试

UseCompressedOops 取消指针压缩功能

1,jdb -XX:+UseSerialGC -Xmx10m -XX:-UseCompressedOops

2,stop in Test.add  表示在Test类的add方法设断点

3,   run Test     开始启动Test类的main方法,Test的main运行后,jdb会在add方法的第一行代码上暂停

4,next,会进入到add方法的第二句代码

执行jps,查看进程号 ,sudo java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

1,查看堆内存 universe

universe

Heap Parameters:

Gen 0:   eden [0x000000011f800000,0x000000011f9458e0,0x000000011fab0000) space capacity = 2818048, 47.31899527616279 used

  from [0x000000011fab0000,0x000000011fab0000,0x000000011fb00000) space capacity = 327680, 0.0 used

  to   [0x000000011fb00000,0x000000011fb00000,0x000000011fb50000) space capacity = 327680, 0.0 usedInvocations: 0

Gen 1:   old  [0x000000011fb50000,0x000000011fb50000,0x0000000120200000) space capacity = 7012352, 0.0 usedInvocations: 0

hsdb>

2,搜索实例 scanoops 

 果然找到了Test的实例 地址 0x000000011f941720   scanoops 命令会显示该实例在JVM 内部对应的instanceOop的内存首地址

hsdb> scanoops 0x000000011f800000 0x0000000120200000 Test

0x000000011f941720 Test

3,选择tools->inspect

4, 查看类的<init>,<clinit> 

    tools-> class brower

 

问题:jvm为什么需要编译器自动生成这2个方法?

答:1,<init>方法等同于类的构造函数,在遇到new指令时,自然会调用该方法

        2,java类中存在static成员变量,或者有static{}的代码块时,jvm加载java类时,会触发<clinit>方法,完成static成员变量的初始化,或者执行static{}的代码逻辑

 

<init>详解

  在上面的Test例子中,并没有显式定义构造方法,但是编译器还是生成了一个构造方法,

 1,如果我们显式的定义了一个构造方法

public class Test{

     private Integer i=3;

     private static int a=90;

     public Test(){

      short s=8;

      }

     public void add(int a,int b){

     Test test=this;

     int z=a+b;

     int x=3;

}

     public static void main(String[] args){

     Test test=new Test();

     test.add(2,3);

    }

修改之前的

public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=1, args_size=1

         0: aload_0           //加载变量表中的this 入栈

         1: invokespecial #1  //调用常量池1位置父类的init方法  // Method java/lang/Object."<init>":()V   

         4: aload_0           //加载变量表中的this 入栈  

         5: iconst_3          //加载3入栈   

         6: invokestatic  #2  //调用常量池2位置的静态方法     //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3  //放到常量池3位置  // Field i:Ljava/lang/Integer;

        12: return

查看字节码

{

  public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=2, args_size=1

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3                  // Field i:Ljava/lang/Integer;

        12: bipush        8                   //将8载入操作数栈 

        14: istore_1                          //存入变量表1位置  

        15: return

}

结论:即使定义了一个构造方法,也不会影响<init>方法,依然会将成员变量的初始化指令加到构造方法的字节码指令中

2,加入一段{}代码块

public class Test{

     private Integer i=3;

     private static int a=90;

    {

     System.out.println("i="+i);

    }

     public Test(){

         short s=8;

      }

     public Test(int x){

        x=12;

    }

     public void add(int a,int b){

     Test test=this;

     int z=a+b;

     int x=3;

     }

 

     public static void main(String[] args){

     Test test=new Test();

     test.add(2,3);

    }

}

字节码内容

{

  public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=3, locals=2, args_size=1

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3     //赋给成员变量值  // Field i:Ljava/lang/Integer;

        //这一段就是{}的执行字节码

        12: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;

        15: new           #5                  // class java/lang/StringBuilder

        18: dup

        19: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V

        22: ldc           #7                  // String i=

        24: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

        27: aload_0

        28: getfield      #3                  // Field i:Ljava/lang/Integer;

        31: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;

        34: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

        37: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        40: bipush        8                   // short s=8

        42: istore_1

        43: return

  public Test(int);

    descriptor: (I)V

    flags: ACC_PUBLIC

    Code:

      stack=3, locals=2, args_size=2

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                    // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3    //赋给成员变量值  // Field i:Ljava/lang/Integer;

        12: getstatic     #4                   // Field java/lang/System.out:Ljava/io/PrintStream;

        15: new           #5                   // class java/lang/StringBuilder

        18: dup

        19: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V

        22: ldc           #7                  // String i=

        24: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

        27: aload_0

        28: getfield      #3                  // Field i:Ljava/lang/Integer;

        31: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;  

        34: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

        37: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        40: bipush        12

        42: istore_1

        43: return

 

总结,从上面的代码,可以看出{}代码的字节码,在两个构造方法都有,进一步地说明,通过new指令,调用构造方法时,都会执行{}代码块

3,加入一个父类 BaseClass.java

public abstract class BaseClass{

    protected long plong=12l;

     static{

      Integer i=30;

    }

}

{

  protected long plong;

    descriptor: J

    flags: ACC_PROTECTED

 

  public BaseClass();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=3, locals=1, args_size=1

         0: aload_0

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

         4: aload_0

         5: ldc2_w        #2                  // long 12l

         8: putfield      #4                  // Field plong:J

        11: return

  static {};

    descriptor: ()V

    flags: ACC_STATIC

    Code:

      stack=1, locals=1, args_size=0

         0: bipush        30

         2: invokestatic  #5       //Integer i=30;    // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         5: astore_0               //保存到本地变量表中

         6: return

}

查看字节码Test.class

{

  public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=2, args_size=1

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3                  // Field i:Ljava/lang/Integer;

        12: bipush        8

        14: istore_1

        15: return

 public Test(int);

    descriptor: (I)V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=2, args_size=2

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3                  // Field i:Ljava/lang/Integer;

        12: bipush        12

        14: istore_1

        15: return

static {};

    descriptor: ()V

    flags: ACC_STATIC

    Code:

      stack=1, locals=0, args_size=0

         0: bipush        90

         2: putstatic     #7                  // Field a:I

         5: return

结论:子类的构造方法,都会先去调用父类的<init>方法,

解释{}与static{}的作用域

1,{}块:

   能够访问java类的非静态成员变量,和静态成员变量,但是其内部定的变量不能被外部所访问,但是{}块的局部变量在编译期会放到本地变量表,而本地变量表又是方法相关的,但是{}块并不一个方法,java编译器将{}块嵌入到<init>方法,因此这个本地变量表,就是java类的构造方法的,

测试当有多个{}块

public class Test extends BaseClass {

     private Integer i=3;

     private static int a=90;

    {

     int a=1;

    int b=2;

    int c=a+b;

     }

    {

    int d=3;

    int e=4;

    int f=e-d;

}

     public Test(){

      short s=8;

      }

}

{

  public Test();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=4, args_size=1

         0: aload_0

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

         4: aload_0

         5: iconst_3

         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

         9: putfield      #3        //成员变量赋值    // Field i:Ljava/lang/Integer;

         //第一个{}代码块

        12: iconst_1

        13: istore_1     // int a=1

        14: iconst_2

        15: istore_2     // int b=2

        16: iload_1

        17: iload_2

        18: iadd         

        19: istore_3     // int c=a+b

         //第二个{}代码块

        20: iconst_3

        21: istore_1     // int d=3

        22: iconst_4

        23: istore_2     // int e=4

        24: iload_2

        25: iload_1

        26: isub         

        27: istore_3     // int f=e-d

        28: bipush        8

        30: istore_1      //short s=8;

        31: return

第一{}中的a的slot是1,第二个{}的d的slot也是1,那不是存在复盖了,这样下次引用不就是错的吗?

由此可见,java类有多个{}块是,编译器并不是简单地将其合并到构造函数,而是将其做为一个独立的普通的java方法,因此多个{}块不受影响

2,static{}块

    仅能访问java类的静态成员变量

    static{}块所定的局部变量不能被 外部访问

clinit方法:

不具有继承性,因为它是在类加载过程中被调用,而父类与子类是分别加载的,当父类加载完之后,父类中的static成员变量初始化,static{}代码块已经执行完成,所以没有必要在子类加载时再执行一次

同样试验多个static{}块

public class Test extends BaseClass {

     private Integer i=3;

     private static int a=90;

     static {

     int a=1;

    int b=2;

    int c=a+b;

     }

     static {

    int d=3;

    int e=4;

    int f=e-d;

}

     public Test(){

      short s=8;

      }

     public void add(int a,int b){

     Test test=this;

     int z=a+b;

     int x=3;

}

     public static void main(String[] args){

     Test test=new Test();

     test.add(2,3);

    }

}

static {};

    descriptor: ()V

    flags: ACC_STATIC

    Code:

      stack=2, locals=3, args_size=0

         0: bipush        90

         2: putstatic     #7                  // Field a:I

          5: iconst_1

         6: istore_0

         7: iconst_2

         8: istore_1

         9: iload_0

        10: iload_1

        11: iadd

        12: istore_2

        13: iconst_3

        14: istore_0

        15: iconst_4

        16: istore_1

        17: iload_1

        18: iload_0

        19: isub

        20: istore_2

        21: return

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值