第4章 JIT编译器

4.1 JIT概览

  语言根据执行的方式不同分为编译型语言和解释型语言。以C++为代表的编译型语言在执行前需要编译成机器码,不同的CPU需要不同的编译器,编译成功后在同一台机器不需再次编译。以Python为代表的解释型语言,解释器一行一行的解释执行Python代码。

  编译型语言的优势在于跨平台,只要平台能够提供相应的解释器都可以执行Python代码。其缺点在于效率,比如对于循环,每次循环都要重新解释执行,而对于编译型代码在循环开始前编译一次无需再次编译。

编译器的优化

  编译器在编译语言的时候需要一定优化,优化的结果是编写的代码和机器码是不同的。优化的目的是更好的利用CPU,降低CPU等待时间。优化的副作用是会在并发编程中带来可见问题。编译器一个重要优化是从内存里取变量优化成从CPU缓存或寄存器里取变量(volatile的味道)。

  

   按理来说sum的值应该在内存里,然后每次计算的时候从内存读到寄存器,然后累加完成写回去。但如果编译器在一开始把sum放到寄存器里,累加完成后写回内存,这样和内存交互只有一次。当然,这种编译器的优化带来的是并发编程的无尽问题。 

Java是怎么搞的

  Java走的是中间路线。javac把java代码编译成字节码,虚拟机解释执行字节码,并在解释执行的过程中把热点代码编译成汇编语言。由于编译的过程是在运行字节码过程中,所以被称为JIT,just in time,即使编译。

为什么不直接编译呢?

  • 如果一段代码只执行一次,编译的时间会大于解释执行的时间,所以只有那些反复执行的代码才有编译的必要,这些反复执行的代码被称为HotSpot。也只有在代码文件执行一定次数后,才能发现哪些代码是hotspot,这也是Java不直接编译的原因。
  • 一段代码执行的次数越多,编译的时候优化的结果就越好。比如java编译成class文件的时候是没有连接这一步的,所有的引用都是符号引用,需要在运行的时候判断真正引用地址。在多次执行代码的过程中能够发现一个符号引用总是指向某一物理地址,那么就可以在编译的时候直接把符号引用编译成物理地址,无需在运行的时候动态确定。

4.2 Client Server选择

  JVM启动参数,区别在于执行class文件时编译的时机不同。

  • client编译代码更早,编译器的优化更少,所以编译的类更多、质量更差。
  • server编译代码晚,编译后的代码“质量”更高,运行更快。

  为什么要这样?

  • 客户端程序生存周期短,需要加载的类较少,代码较为简单,更适合解释执行。而Server端程序复杂,运行时间更久,所以更晚质量更高的编译收获更大。
  • 客户端对启动时间更敏感。

 分层编译

  把server client统一起来的一种编译技术,在程序刚刚启动的时候使用client编译,等到程序热起来再切换成server。java8中默认开启分层编译。

4.4 与编译有关的虚拟机参数

  编译器缓存

  编译好的代码需要在JVM里缓存起来,当缓存被填满的时候就无法继续编译后续的热点代码。需要根据业务合理的提高该值。

  • -XX:ReservedCodeCacheSize 缓存最大值
  • -XX:initialCodeCacheSize 缓存初始值

  缓存从初始值开始分配符,最大分配到最大值。

  值得注意的是分层编译缓存很容易超出上限,尤其在Java7。

原文地址:https://www.cnblogs.com/AshOfTime/p/10840537.html