乐趣区

关于java:jvm-类的编译

编译

咱们都晓得,当咱们编写完代码后,能够用 javac 命令或者开发工具,比方 eclipse、idea 等,把 java 文件编译成 class 文件,java 虚拟机才能够执行。下图是 WinHex 关上 class 文件 16 进制的字节码。

每个 class 文件的后面四个字节成为魔数,它的惟一作用就是确定这个文件是否能被虚拟机承受的文件,这个魔数值为 0xCSAFEBABE(咖啡宝贝)。第 5、6 字节是次版本号,第 7、8 字节是主版本号。从上图能够看到,我的主版本号是 16 进制的 34,也就是 10 进制的 52,52 对应的 JDK 版本是 1.8。
前面的 16 进制在这里不做过多的解说,能够依据 Java 虚拟机标准的约定,以无符号数和表两种数据类型进行解析。
java 文件编译成 class 文件的时候,先依据词法和语法分析取得了程序代码的形象语法树示意,填充符号表,而后依据语义剖析看程序是否合乎逻辑,比方变量在应用前是否曾经申明,变量是否正确赋值等,最初就是把后面步骤生成的信息(比方语法树),转换成字节码写到磁盘中,当然编译器还做了大量的代码增加(程序中没有构造函数,会增加无参构造函数的操作是在填充符号表实现的,不是这里实现)和转换工作。

编译优化

常量折叠

请看上面的例子:

public class HelloWord {public static void main(String[] args) {System.out.println("Hello," + "World");
     }
}

如果我把下面的代码,改成这样的,那么我在运行期的时候,会减轻虚拟机的累赘吗?答案是否定的。编译器除了查看外,还对常量进行了折叠,也就是说,下面的 Hello World 会在编译的时候主动拼接,如果是 int 型的,比方 i = 1 + 2,等价于 i = 3,并不会减少程序在运行期哪怕一个 CPU 指令的运算量。
咱们看看上面这个,如果是 str +=“c”呢,会不会折叠呢?

public void fun() {
    String str = "a" + "b";
    str += "c";
    System.out.println(str);
}

咱们从下图的 ASTView 能够看出,标记 1 和标记 2 是不一样的类型,一个是变量赋值,第二个是表达式,在标记 3 能够看到,变量赋值的时候,主动把 a 和 b 拼接起来。

泛型的擦除

看上面的例子:

public static void main(String[] args) {List<String> list1 = new ArrayList<>();
    List<Map<String, Integer>> list2 = new ArrayList<>();
    System.out.println(list1);
    System.out.println(list2);
}

反编译后,如下,竟然泛型还在,其实这个出乎我的意料之外,两年前给公司分享虚拟机的时候,我的 ppt 里还记录着泛型的擦除的案例,反编译后的确的没有泛型的。

  public static void main(String[] args) {List<String> list1 = new ArrayList<String>();
    List<Map<String, Integer>> list2 = new ArrayList<Map<String, Integer>>();
    System.out.println(list1);
    System.out.println(list2);
  }

于是我又换了另外一个反编译器,反编译如下,这次的确没有泛型信息。

  public static void main(String[] args)
  {List list1 = new ArrayList();
    List list2 = new ArrayList();
    System.out.println(list1);
    System.out.println(list2);
  }

用 java 自带的命令 javap -verbose 进行反编译,后果如下,能够看到泛型信息在只有调试用的 LocalVariableTypeTable(LVTT)里,某些反编译器应该依据 LVTT 来展现泛型信息。

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      31     0  args   [Ljava/lang/String;
    8      23     1 list1   Ljava/util/List;
   16      15     2 list2   Ljava/util/List;
LocalVariableTypeTable:
Start  Length  Slot  Name   Signature
    8      23     1 list1   Ljava/util/List<Ljava/lang/String;>;
   16      15     2 list2   Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/Integer;>;>;

因为泛型在编译期就会被擦除,所以以下的重载,是编译不过来的,因为擦除后,他们其实

public static String fun(List<String> list) {return null;}

public static Integer fun(List<Integer> list) {return null;}

主动装箱、拆箱与循环遍历

这部分代码包含主动装箱、拆箱与循环遍历。

public void fun() {List<Integer> list = Arrays.asList(1, 2, 3, 4);
    int sum = 0;
    for (int i : list) {sum += i;}
    System.out.println(sum);
}

反编译后如下,首先把 int 装箱变成 Integer,而后计算的时候,再从 Integer 装配变成 int。另外一个就是把 for 编译成 Iterator 迭代器。

  public void fun()
  {List list = Arrays.asList(new Integer[] {Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
    int sum = 0;
    for (Iterator localIterator = list.iterator(); localIterator.hasNext();) {int i = ((Integer)localIterator.next()).intValue();
      sum += i;
    }
    System.out.println(sum);
  }

另外看看上面的例子

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 4;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    System.out.println(c == d);
    System.out.println(e == f);
    System.out.println(c == (a + b));
    System.out.println(c.equals(a + b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
}

反编译后

  public static void main(String[] args)
  {Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    Integer c = Integer.valueOf(3);
    Integer d = Integer.valueOf(4);
    Integer e = Integer.valueOf(321);
    Integer f = Integer.valueOf(321);
    Long g = Long.valueOf(3L);
    // 为 false,Integer 对象,比拟地址,两个地址不一样
    System.out.println(c == d);
    // 为 false,因为两个地址不一样
    System.out.println(e == f);
    // 为 true,都变成 int,比拟值
    System.out.println(c.intValue() == a.intValue() + b.intValue());
   // 为 true,equals 办法比拟 int System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
   // 为 true,比拟值
    System.out.println(g.longValue() == a.intValue() + b.intValue());
    // 为 false,类型不一样
    System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
  }

条件编译

上面这个包含没有应用的变量 a,if 语句判断,while 语句。

public static void main(String[] args) {
    int a;
    if(true){System.out.println("Hello");
    }else{System.out.println("World");
    }
   /* while(false){
        // Unreachable statement
        System.out.println("while");
    }*/
}

反编译后,没有应用的变量 a 并没有被编译,Dead code,也就是为 false 的局部也没有被编译,另外 while 那个,因为恒为 false,编译器报 Unreachable statement。

  public static void main(String[] args)
  {System.out.println("Hello");
  }
退出移动版