• JVM字节码(四)


    之前分析的MyTest1程序是比较简单的程序,接来下我们将再用一个程序来巩固一下对JVM字节码的理解:

    package com.leolin.jvm.bytecode;
    
    public class MyTest2 {
        String str = "Welcome";
        private int x = 5;
        public static Integer in = 10;
        private Object obj = new Object();
    
        public MyTest2() {
        }
    
        public MyTest2(int a) {
        }
    
    
        public static void main(String[] args) {
            MyTest2 myTest2 = new MyTest2();
            myTest2.setX(8);
            in = 20;
        }
    
        private synchronized void setX(int x) {
            this.x = x;
        }
    
        private void test(String str) {
            synchronized (obj) {
                System.out.println("str:" + str);
            }
        }
    
        static {
            System.out.println("hello");
        }
    }
    

      

    上面的代码如果从Java语言层面来看,是比较简单的,但是经过编译之后,对应的字节码文件,就不一定了。这里我们编译后再用javap -verbose -p来看看反编译之后的方法,这里之所比相比以前加上一个-p,是因为如果单单使用-verbose不会打印我们另外两个私有方法。反编译结果如下:

    D:Fjava_spacejvm-lecture	argetclasses>javap -verbose -p com.leolin.jvm.bytecode.MyTest2
    Classfile /D:/F/java_space/jvm-lecture/target/classes/com/leolin/jvm/bytecode/MyTest2.class
      Last modified 2020-5-14; size 1578 bytes
      MD5 checksum e247d6b8aea41e469b1ebcd282f8eabb
      Compiled from "MyTest2.java"
    public class com.leolin.jvm.bytecode.MyTest2
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #5.#53         // java/lang/Object."<init>":()V
       #2 = String             #54            // Welcome
       #3 = Fieldref           #7.#55         // com/leolin/jvm/bytecode/MyTest2.str:Ljava/lang/String;
       #4 = Fieldref           #7.#56         // com/leolin/jvm/bytecode/MyTest2.x:I
       #5 = Class              #57            // java/lang/Object
       #6 = Fieldref           #7.#58         // com/leolin/jvm/bytecode/MyTest2.obj:Ljava/lang/Object;
       #7 = Class              #59            // com/leolin/jvm/bytecode/MyTest2
       #8 = Methodref          #7.#53         // com/leolin/jvm/bytecode/MyTest2."<init>":()V
       #9 = Methodref          #7.#60         // com/leolin/jvm/bytecode/MyTest2.setX:(I)V
      #10 = Methodref          #61.#62        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      #11 = Fieldref           #7.#63         // com/leolin/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;
      #12 = Fieldref           #64.#65        // java/lang/System.out:Ljava/io/PrintStream;
      #13 = Class              #66            // java/lang/StringBuilder
      #14 = Methodref          #13.#53        // java/lang/StringBuilder."<init>":()V
      #15 = String             #67            // str:
      #16 = Methodref          #13.#68        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #17 = Methodref          #13.#69        // java/lang/StringBuilder.toString:()Ljava/lang/String;
      #18 = Methodref          #70.#71        // java/io/PrintStream.println:(Ljava/lang/String;)V
      #19 = String             #72            // hello
      #20 = Utf8               str
      #21 = Utf8               Ljava/lang/String;
      #22 = Utf8               x
      #23 = Utf8               I
      #24 = Utf8               in
      #25 = Utf8               Ljava/lang/Integer;
      #26 = Utf8               obj
      #27 = Utf8               Ljava/lang/Object;
      #28 = Utf8               <init>
      #29 = Utf8               ()V
      #30 = Utf8               Code
      #31 = Utf8               LineNumberTable
      #32 = Utf8               LocalVariableTable
      #33 = Utf8               this
      #34 = Utf8               Lcom/leolin/jvm/bytecode/MyTest2;
      #35 = Utf8               (I)V
      #36 = Utf8               a
      #37 = Utf8               main
      #38 = Utf8               ([Ljava/lang/String;)V
      #39 = Utf8               args
      #40 = Utf8               [Ljava/lang/String;
      #41 = Utf8               myTest2
      #42 = Utf8               setX
      #43 = Utf8               test
      #44 = Utf8               (Ljava/lang/String;)V
      #45 = Utf8               StackMapTable
      #46 = Class              #59            // com/leolin/jvm/bytecode/MyTest2
      #47 = Class              #73            // java/lang/String
      #48 = Class              #57            // java/lang/Object
      #49 = Class              #74            // java/lang/Throwable
      #50 = Utf8               <clinit>
      #51 = Utf8               SourceFile
      #52 = Utf8               MyTest2.java
      #53 = NameAndType        #28:#29        // "<init>":()V
      #54 = Utf8               Welcome
      #55 = NameAndType        #20:#21        // str:Ljava/lang/String;
      #56 = NameAndType        #22:#23        // x:I
      #57 = Utf8               java/lang/Object
      #58 = NameAndType        #26:#27        // obj:Ljava/lang/Object;
      #59 = Utf8               com/leolin/jvm/bytecode/MyTest2
      #60 = NameAndType        #42:#35        // setX:(I)V
      #61 = Class              #75            // java/lang/Integer
      #62 = NameAndType        #76:#77        // valueOf:(I)Ljava/lang/Integer;
      #63 = NameAndType        #24:#25        // in:Ljava/lang/Integer;
      #64 = Class              #78            // java/lang/System
      #65 = NameAndType        #79:#80        // out:Ljava/io/PrintStream;
      #66 = Utf8               java/lang/StringBuilder
      #67 = Utf8               str:
      #68 = NameAndType        #81:#82        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #69 = NameAndType        #83:#84        // toString:()Ljava/lang/String;
      #70 = Class              #85            // java/io/PrintStream
      #71 = NameAndType        #86:#44        // println:(Ljava/lang/String;)V
      #72 = Utf8               hello
      #73 = Utf8               java/lang/String
      #74 = Utf8               java/lang/Throwable
      #75 = Utf8               java/lang/Integer
      #76 = Utf8               valueOf
      #77 = Utf8               (I)Ljava/lang/Integer;
      #78 = Utf8               java/lang/System
      #79 = Utf8               out
      #80 = Utf8               Ljava/io/PrintStream;
      #81 = Utf8               append
      #82 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #83 = Utf8               toString
      #84 = Utf8               ()Ljava/lang/String;
      #85 = Utf8               java/io/PrintStream
      #86 = Utf8               println
    {
      java.lang.String str;
        descriptor: Ljava/lang/String;
        flags:
    
      private int x;
        descriptor: I
        flags: ACC_PRIVATE
    
      public static java.lang.Integer in;
        descriptor: Ljava/lang/Integer;
        flags: ACC_PUBLIC, ACC_STATIC
    
      private java.lang.Object obj;
        descriptor: Ljava/lang/Object;
        flags: ACC_PRIVATE
    
      public com.leolin.jvm.bytecode.MyTest2();
        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: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: aload_0
            16: new           #5                  // class java/lang/Object
            19: dup
            20: invokespecial #1                  // Method java/lang/Object."<init>":()V
            23: putfield      #6                  // Field obj:Ljava/lang/Object;
            26: return
          LineNumberTable:
            line 9: 0
            line 4: 4
            line 5: 10
            line 7: 15
            line 10: 26
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      27     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
    
      public com.leolin.jvm.bytecode.MyTest2(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: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: aload_0
            16: new           #5                  // class java/lang/Object
            19: dup
            20: invokespecial #1                  // Method java/lang/Object."<init>":()V
            23: putfield      #6                  // Field obj:Ljava/lang/Object;
            26: return
          LineNumberTable:
            line 12: 0
            line 4: 4
            line 5: 10
            line 7: 15
            line 13: 26
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      27     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
                0      27     1     a   I
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #7                  // class com/leolin/jvm/bytecode/MyTest2
             3: dup
             4: invokespecial #8                  // Method "<init>":()V
             7: astore_1
             8: aload_1
             9: bipush        8
            11: invokespecial #9                  // Method setX:(I)V
            14: bipush        20
            16: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            19: putstatic     #11                 // Field in:Ljava/lang/Integer;
            22: return
          LineNumberTable:
            line 17: 0
            line 18: 8
            line 19: 14
            line 20: 22
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      23     0  args   [Ljava/lang/String;
                8      15     1 myTest2   Lcom/leolin/jvm/bytecode/MyTest2;
    
      private synchronized void setX(int);
        descriptor: (I)V
        flags: ACC_PRIVATE, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #4                  // Field x:I
             5: return
          LineNumberTable:
            line 23: 0
            line 24: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
                0       6     1     x   I
    
      private void test(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PRIVATE
        Code:
          stack=3, locals=4, args_size=2
             0: aload_0
             1: getfield      #6                  // Field obj:Ljava/lang/Object;
             4: dup
             5: astore_2
             6: monitorenter
             7: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            10: new           #13                 // class java/lang/StringBuilder
            13: dup
            14: invokespecial #14                 // Method java/lang/StringBuilder."<init>":()V
            17: ldc           #15                 // String str:
            19: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            22: aload_1
            23: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            26: invokevirtual #17                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            29: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            32: aload_2
            33: monitorexit
            34: goto          42
            37: astore_3
            38: aload_2
            39: monitorexit
            40: aload_3
            41: athrow
            42: return
          Exception table:
             from    to  target type
                 7    34    37   any
                37    40    37   any
          LineNumberTable:
            line 27: 0
            line 28: 7
            line 29: 32
            line 30: 42
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      43     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
                0      43     1   str   Ljava/lang/String;
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 37
              locals = [ class com/leolin/jvm/bytecode/MyTest2, class java/lang/String, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: bipush        10
             2: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             5: putstatic     #11                 // Field in:Ljava/lang/Integer;
             8: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            11: ldc           #19                 // String hello
            13: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            16: return
          LineNumberTable:
            line 6: 0
            line 33: 8
            line 34: 16
    }
    SourceFile: "MyTest2.java"
    

      

    首先,我们来分析两个构造方法:

      public com.leolin.jvm.bytecode.MyTest2();
        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: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: aload_0
            16: new           #5                  // class java/lang/Object
            19: dup
            20: invokespecial #1                  // Method java/lang/Object."<init>":()V
            23: putfield      #6                  // Field obj:Ljava/lang/Object;
            26: return
          LineNumberTable:
            line 9: 0
            line 4: 4
            line 5: 10
            line 7: 15
            line 10: 26
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      27     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
    
      public com.leolin.jvm.bytecode.MyTest2(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: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: aload_0
            16: new           #5                  // class java/lang/Object
            19: dup
            20: invokespecial #1                  // Method java/lang/Object."<init>":()V
            23: putfield      #6                  // Field obj:Ljava/lang/Object;
            26: return
          LineNumberTable:
            line 12: 0
            line 4: 4
            line 5: 10
            line 7: 15
            line 13: 26
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      27     0  this   Lcom/leolin/jvm/bytecode/MyTest2;
                0      27     1     a   I
    

      

    第一个是无参构造方法,但很奇怪的是,第一个构造方法的参数数量args_size为1。这是因为Java在调用实例方法时,每个方法的第一个参数都是接收一个this,而Java会隐式的帮我们把当前对象传入实例方法。比如下面的A类:

    class A {
        public void test() {
        }
    }
    

      

    当我们调用一个A的实例方法test时,会转换成这样的形式:A.test(this),同理构造方法。这样我们也就能够理解,为什么MyTest2第二个构造方法的的参数数量args_size为2。第一个是Java帮我们隐式传入的this,第二个是我们显式声明的int类型变量a。我们知道,如果我们在类里为成员变量赋值,这些赋值的动作其实是在构造方法里完成。这里我们可以看到,两个构造方法中对应的字节码指令其实是一样的,也就是说,给成员变量赋值的字节码指令,会在每一个构造方法中冗余一份。我们来看下构造方法中的字节码指令:

     0: aload_0
     1: invokespecial #1                  // Method java/lang/Object."<init>":()V
     4: aload_0
     5: ldc           #2                  // String Welcome
     7: putfield      #3                  // Field str:Ljava/lang/String;
    10: aload_0
    11: iconst_5
    12: putfield      #4                  // Field x:I
    15: aload_0
    16: new           #5                  // class java/lang/Object
    19: dup
    20: invokespecial #1                  // Method java/lang/Object."<init>":()V
    23: putfield      #6                  // Field obj:Ljava/lang/Object;
    26: return
    

      

    在构造方法中,先执行aload_0指令加载this的引用到栈顶,再执行invokespecial指令调用this的父类Object的构造方法,完成父类的初始化。执行完偏移0和1的指令之后,this引用会从栈中弹出,所以在此执行aload_0加载this到栈顶,然后执行ldc指令,加载常量池的元素第二个元素:Welcome到栈顶。putfield指令接收一个参数,即要在对象中设置值的字段,然后从栈中弹出两个元素,即是this引用和Welcome,完成成员变量str的赋值,这是偏移4到7所做的事。接着在偏移10到12完成字段x的赋值。在偏移15到23之间,依旧是加载this到栈顶,用new指令创建一个Object的对象然后推至栈顶,接着调用dup指令,根据栈顶的Object对象引用再次创建一份引用推至栈顶,至此栈顶有连续两个同一Object对象的引用,执行invokespecial指令,会调用Object对象的构造方法,然后弹出Object对象的引用,此时栈顶还有一个Object对象和this对象的引用,执行putfield指令,将栈中的两个引用弹出,把Object对象的引用设置进this对象的obj字段中。最后,执行偏移26的指令return,构造方法结束。

    接着,我们分析main方法:

      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #7                  // class com/leolin/jvm/bytecode/MyTest2
             3: dup
             4: invokespecial #8                  // Method "<init>":()V
             7: astore_1
             8: aload_1
             9: bipush        8
            11: invokespecial #9                  // Method setX:(I)V
            14: bipush        20
            16: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            19: putstatic     #11                 // Field in:Ljava/lang/Integer;
            22: return
          LineNumberTable:
            line 17: 0
            line 18: 8
            line 19: 14
            line 20: 22
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      23     0  args   [Ljava/lang/String;
                8      15     1 myTest2   Lcom/leolin/jvm/bytecode/MyTest2;
    

      

    因为main方法是静态方法,静态方法就不像之前的实例方法,会隐式传入一个this,所以main方法设定传入一个数组参数,args_size即为1。首先,偏移0~7完成将创建的MyTest2实例压入栈顶,复制一份实例的引用压入栈顶,然后弹出引用执行构造方法,之后执行astore_1弹出MyTest2实例,将其保存在局部变量表索引为1的位置,,至此,操作数栈中不存在任何元素。

    然后在偏移8到11之间,先执行aload_1,将局部变量表索引为1的引用加载到栈顶,这里是之前创建的MyTest2实例,再执行bipush,将一个字节长度的数字推送到栈顶,这里是8。之后调用invokespecial,从栈中弹出两个元素,将8作为参数传入MyTest2实例的方法setX(int x)。

    最后在偏移14到22之间,执行bipush,将20推送至栈顶,然后调用invokestatic指令,执行Integer类中的静态方法valueOf(String s),将原先栈顶的元素20弹出传入该方法中,得到一个Integer对象后压入栈顶。执行putstatic,从栈顶弹出Integer对象,这只为类中静态变量in的值。

    接下来,我们来看我们定义的方法setX(int x)和void text(),这两个都是同步方法:

      ……
      private synchronized void setX(int);
        descriptor: (I)V
        flags: ACC_PRIVATE, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #4                  // Field x:I
             5: return
      ……
      
      private void test(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PRIVATE
        Code:
          stack=3, locals=4, args_size=2
             0: aload_0
             1: getfield      #6                  // Field obj:Ljava/lang/Object;
             4: dup
             5: astore_2
             6: monitorenter
             7: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            10: new           #13                 // class java/lang/StringBuilder
            13: dup
            14: invokespecial #14                 // Method java/lang/StringBuilder."<init>":()V
            17: ldc           #15                 // String str:
            19: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            22: aload_1
            23: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            26: invokevirtual #17                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            29: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            32: aload_2
            33: monitorexit
            34: goto          42
            37: astore_3
            38: aload_2
            39: monitorexit
            40: aload_3
            41: athrow
            42: return
      ……
    

      

    首先是setX(int x),我们在方法的声明处加上synchronized,因此方法的访问标志就有ACC_SYNCHRONIZED。当有多个线程调用setX(int x)方法时,该方法是通过对MyTest2的class对象加锁和释放锁来实现的同步。

    而在test()方法中,我们是通过synchronized块对实例内的对象obj上锁来实现同步的,这里有两个我们之前没见过的命令monitorenter和monitorexit。每个对象都会关联一个监视器,监视器有个条目计数,执行monitorenter指令时进入对象的监视器,如果监视器的条目计数为0,则设置为1,当前线程为监视器的所有者。线程还可以进入其他的synchronized块,如果synchronized块上锁的对象所关联的监视器归当前线程所拥有,会对监视器的条目计数加1。其他线程如果想拥有监视器的所有权,必须等到监视器条目为0的时候,才可以获取监视器的所有权。

    那我们很容易想到monitorexit的作用,当线程离开一个synchronized块时,synchronized块所关联的监视器的条目计数就会减1。当监视器的条目计数减为0时,代表监视器此时无无主的状态,其他因为监视器而陷入阻塞的线程就能获得该监视器的所有权。我们注意到,上面的monitorenter指令只有一条,而monitorexit却不止一条。这是因为synchronized块的入口只有一个,出口却可以有好几个,如果代码没有异常的话,synchronized块应该是从上执行到下,先执行monitorenter对监视器的条目计数加1,最后执行monitorexit对监视器条目减1,这是最理想的状态。但是我们的程序是有可能抛出异常的,当异常抛出时,我们也要对监视器的条目计数减1,这就要求Java要能观察出程序中所有有可能抛出异常的地方,当异常发生要对监视器的条目计数减1,否则其他需要监视器的线程将永远陷入阻塞的状态。

    最后是我们的静态代码块,在一个类中,会在一个名为clinit的静态方法中去执行所有静态变量的赋值、以及静态代码块的执行。根据之前类加载机制,我们知道类的加载和链接,只是完成静态变量内存的分配,以及赋予静态变量默认值,比如int的默认值为0,对象的默认值为null。但是在初始化的时候,才会将程序员赋予变量的值,赋予静态变量,即在clinit中。

      ……
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: bipush        10
             2: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             5: putstatic     #11                 // Field in:Ljava/lang/Integer;
             8: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            11: ldc           #19                 // String hello
            13: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            16: return
          LineNumberTable:
            line 6: 0
            line 33: 8
            line 34: 16
      ……
    

      

    在MyTest2这个类中,我们声明了一个Integer类型的静态变量in,而且还有一个静态代码块,静态代码块中我们打印了一个hello。可以看到,上面偏移0到5和偏移8到13分别对应静态变量in赋值为10和获取IO流打印hello。至此,MyTest2的方法分析完毕。

  • 相关阅读:
    本月时间按天显示
    微信小程序----当前时间的时段选择器插件(今天、本周、本月、本季度、本年、自定义时段)
    vuex进行传值
    echart 自定义 formatter
    git 登录流程
    Java学习-反射
    mysql数据类型char、varchar、text的一些区别
    微信小程序踩坑记录
    Rancher、Helm、HelmFile
    句子迷 2015_01_10
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/12889185.html
Copyright © 2020-2023  润新知