同样是先调了父类Object的构造方法, 然后再将this, 99入栈, invokevirtual #2旁边注释了是调用setX, 参数分别是this和99也是this.setX(99), 然而这个方法被重写了, 调用的是子类的方法, 所以我们再看SubClass.setX:
  public class bugme.SubClass extends bugme.SuperClass {
  ......
  public void setX(int);
  Code:
  0: aload_0
  1: iload_1
  2: invokespecial #3                  // Method bugme/SuperClass.setX:(I)V
  ......
  }
  这里将局部变量表前两个元素都入栈, 第一个是this, 第二个是括号里的参数, 也是99, invokespecial #3调用的是父类的setX, 也是我们代码中写的super.setX(int)
  SuperClass.setX很简单了:
  public class bugme.SuperClass {
  ......
  public void setX(int);
  Code:
  0: aload_0
  1: iload_1
  2: putfield      #3                  // Field mSuperX:I
  5: return
  }
  这里先把this入栈, 再把参数入栈, putfield #3使得前两个入栈的元素全部出栈, 而成员mSuperX被赋值, 这四条指令只对应代码里的一句this.mSuperX = x;
  接下来控制流回到子类的setX:
public class bugme.SubClass extends bugme.SuperClass {
......
public void setX(int);
Code:
0: aload_0
1: iload_1
2: invokespecial #3                  // Method bugme/SuperClass.setX:(I)V
->5: aload_0                           // 即将执行这句
6: iload_1
7: putfield      #2                  // Field mSubX:I
10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
13: new           #5                  // class java/lang/StringBuilder
16: dup
17: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
20: ldc           #7                  // String SubX is assigned
22: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: iload_1
26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
}
  从5处开始继续分析, 5,6,7将参数的值赋给mSubX, 此时mSubX是99了, 下面那一堆则是在执行System.out.println("SubX is assigned " + x);并返回, 还可以看到Java自动帮我们使用StringBuilder优化字符串拼接, 不分析了.
  说了这么多, 我们的代码才刚把下面箭头指着的这句执行完:
  public class bugme.SubClass extends bugme.SuperClass {
  public bugme.SubClass();
  Code:
  0: aload_0
  ->1: invokespecial #1                  // Method bugme/SuperClass."<init>":()V
  4: aload_0
  5: iconst_1
  6: putfield      #2                  // Field mSubX:I
  9: return
  ......
  }
  此时mSubX已经是99了, 再执行下面的4,5,6, 这一部分是SubClass的初始化, 代码将把1赋给mSubX, 99被1覆盖了.
  方法返回后, 相当于我们执行完了箭头指的这一句代码:
  public class Main {
  public static void main(String[] args) {
  ->SubClass sc = new SubClass();
  sc.printX();
  }
  }
  接下来执行的代码将打印mSubX的值, 自然是1了.
  以前听说过JVM是基于栈的, Dalvik是基于寄存器的, 现在看了Java字节码, 回想一下smali, 自然能明白. 我在Android无需权限显示悬浮窗, 兼谈逆向分析app中有分析smali代码, smali里面经常看到类似v0, v1这类东西, 是在操作寄存器, 而刚才分析的bytecode, 指令常常伴随着入栈出栈.
  理论解释
  我们都知道Java是面向对象的语言, 面向对象三大特性之一多态性. 假如父类构造方法中调用了某个方法, 这个方法恰好被子类重写了, 会发生什么?
  根据多态性, 实际被调用的是子类的方法, 这个没错. 再考虑有继承时, 初始化的顺序. 如果是new一个子类, 那么初始化顺序是:
  父类static成员 -> 子类static成员 -> 父类普通成员初始化和初始化块 -> 父类构造方法 -> 子类普通成员初始化和初始化块 -> 子类构造方法
  父类构造方法中调用了一次setX, 此时mSubX中已经是我们要跟踪的值, 但之后子类普通成员初始化将mSubX又初始化了一遍, 覆盖了前面我们跟踪的值, 自然得到的值是错的.
  Java中, 在构造方法中能安全调用的是基类中的final方法, 自己的final方法(自己的private方法自动final), 如果类本身是final的, 自然能安全调用自己所有的方法.
  完全遵守这个准则, 可以保证不会出这个bug. 实际上我们常常不能遵守, 所以要时刻小心这个问题.
  这个东西在Java编程思想(第四版) (机械工业出版社 2012年11月第1版) 的8.3.3小节有写过, 但是这种东西除非自己遇到bug了, 基本看过不会有印象.
  这篇文章所有的知识点基本都是很基础的, 我自己也都记得, 但当这些知识合在一起的时候, 他们之间产生的反应却是我没有注意过的. 这也是我写这篇文章的原因.
  如果以后有人面试拿这个问题考你, 你可能是遇上drakeet了.
  题外话
  关于默认初始化, 比如这样写:
  public class SubClass extends SuperClass {
  private int mSubX;
  public SubClass() {}
  ......
  }
  如果父类保证一定会在初始化时调用setX, 程序是不会出现上面说的bug的, 因为默认初始化并不是靠生成下面这样的代码默认初始化.
  4: aload_0
  5: iconst_1
  6: putfield      #2                  // Field mSubX:I
  所谓的默认初始化, 其实是我们要实例化一个对象之前, 需要一块内存放我们的数据, 这块内存被全部置为0, 这是默认初始化了.
  下面这两句话, 虽然效果一样, 但实际是有区别的.
  private int mSubX;
  private int mSubX = 0;
  一般情况下, 这两句代码对程序没有任何影响(除非你遇到这个bug), 上面一句和下面一句的区别在于, 下面一句会导致<init>方法里面生成3条指令, 分别是aload_0, iconst_0, putfield #**, 而上面一句则不会.
  所以如果你的成员变量使用默认值初始化, 没必要自己赋那个默认值, 而且还能省3条指令.