深入理解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
      LineNumberTable:
        line 1: 0
        line 3: 4
 
  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   scan oops 命 令会显示该实例在 JVM 内部 对应的 instan ceOop 的内存首地址
hsdb> scanoops 0x000000011f800000 0x0000000120200000 Test
0x000000011f941720 Test
hsdb>
 
3,选择tools->inspect
55f1142935cf53da5674535ffe8ba820be7.jpg
4, 查看类的<init>,<clinit> 
    tools-> class brower
71cf6498240e2931752c30df91efb6f72ed.jpg
 
问题: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>详解
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
}

转载于:https://my.oschina.net/u/3699155/blog/2877233

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值