Java 的编译器有三类前端
下面简要介绍下 Java 的编译与优化java
[TOC]程序员
几乎是全部编译器的第一步,通过这一步,编译器能够生成初始语法树,由于注解的存在,最终语法树的生成在注解处理以后数组
Java 的泛型是伪泛型缓存
C++ 中的模板是泛型的一种形式,编译时不一样类型的泛型参数会促使模板生成新的代码,这种经过类型膨胀的形式实现的泛型是真实泛型函数
Java 的泛型只展示在代码中,编译成字节码后泛型信息就已经丢失了(底层使用 Object 引用实际对象,实际使用时会有强制类型转换的过程),下面两行代码在相同的 java 文件中时是没法经过编译的,由于 class 文件中不容许出现签名相同的函数(此时泛型信息已经丢失)性能
public void method(List<String> list) { ... } public void method(List<Integer> list) { ... }
在 C++ 中,上面的两个函数在编译时的签名是不一样的,但在 Java 的 class 文件中,这两个函数的签名相同,故编译器报错优化
class 格式规定,只要描述符不彻底一致的两个方法就能够共存线程
Java 中类型的描述符是包含返回值的,也就是说只要修改上面那两个方法中的返回值,这两个方法就能够共存,这是设计中的 缺陷,虽然对程序的正常运行没有影响,但违反了返回值不参与重载的规定设计
Java 新标准中提出了一些新的规范来减小这种状况对语言的影响,如 Signature 等
拆箱与装箱的陷阱
public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); // true,Java的==比的是引用地址,小整数有缓存故地址相同 System.out.println(e == f); // false,默认状况下 Java 缓存 -128~127 之间的全部整数 System.out.println(c == (a + b)); // true,有运算符,故先拆箱后比较 System.out.println(c.equals(a + b)); // true,同类比较 System.out.println(g == (a + b)); // true,包装类的 == 只会在遇到算术运算符时拆箱 System.out.println(g.equals(a + b)); // false,异类比较,equals不处理类型转换,故long!=int }
Java 的条件编译很简单,只能使用 if 且 if 的条件必须为常量,编译后 class 文件中只会留下知足需求的语句块
JDK 5 以后 Java 提供了对注解的支持,Java 支持编译时注解和运行时注解,使用编译时注解咱们能够干涉编译器的行为
部分商用虚拟机中 Java 程序最开始执行时使用解释器进行解释执行,若是发现一块代码执行频繁,JVM 会使用 JIT (即时编译)对其进行编译,转化为平台相关的机器码并进行各类优化以提升执行效率
HotSpot 有两个即时编译器,分别称为 Client Compiler 和 Server Compiler,或者简称 C1 和 C2 编译器,JVM 会根据运行模式选择不一样的编译器,用户能够经过 -client 或者 -server 指定编译器。固然也能够强制禁止 JVM 使用运行时编译器,JVM 全程使用解释方式运行
OSR(On Stack Replacement),JIT 编译并优化某个方法的形象说法,优化的方法都位于栈,方法优化即替换已有的解释执行方法,即栈上替换
判断一个函数是否是热点代码片断有两种方式:基于采样的热点探测和基于计数器的热点探测。前者 JVM 周期性的检查栈顶函数类型,后者 JVM 会为每个函数维护一个计算器,后者更精确严谨些
HotSpot 使用第二种方式并使用了两类计数器:
当前大部分 JVM 设计都把代码的优化重心放在了 JIT 上,除非特殊状况,不要关闭 JIT
JIT 编译使用了大量编译技术,本文不作过多介绍,下面仅给出几个便于理解的方法
C++ 为静态编译语言而 Java 为动态编译,两者编译器各有优劣。C++ 能够在编译时进行比较耗时的优化而Java却不行,由于这样会影响服务性能;Java 能够搜集大量运行时信息(调用频率、分支频率预测、裁剪未选中分支等等)来优化代码但 C++ 不行。其余对比能够查阅周志明《深刻理解 Java 虚拟机》