注意,这里强调的是"书写一次"而非"编译一次",重心并不在"编译"上,这句话是对比c语言的,c语言的书写过程中有许多对系统层面API的调用,不同的操作系统的系统调用封装函数是不同的,所以每次换系统的时候都要更改c语言的源文件。针对java而言,因为虚拟机屏蔽了底层细节,所以用java写出来的代码都是一样的。
下图为Java源文件转化至机器码的过程

Java有编译执行也有解释执行,针对热点代码,jvm 中提供了动态编译器JIT,可以在运行时将代码编译为机器码,这种情况下热点代码就属于编译执行了。
其完整过程是Java源文件首先通过javac编译成为字节码文件,即.class文件,这是可执行文件,接着在运行时JIT将热点代码编译为机器码,剩余代码则通过jvm内嵌的解释器将其解释为机器码。
还有一种新的编译方式,即AOT(Ahead-of-Time Compilation),这种编译方式直接将字节码编译成机器代码,避免了JIT预热等各方面的开销,在JDK9中进行了实验,并且增加了新的jaotc工具来进行编译。
判断一段代码是否为热点代码有两种方法:
周期性地去方法栈顶查看被调用的方法,如果是经常出现的就会视为热点代码,这种方法实现简单,但是不精确,容易受线程阻塞和外界因素影响。
hotspot采用此方法,jvm会为方法或者代码创建计数器,这种计数器有两种,一种是方法调用计数器,一种是回边计数器。
其中方法调用计数器是统计一个方法在一定时间内的执行频率,如果在一段时间内两个计数器的值的总和超过某个阈值,jvm就确认其为热点代码,但超过一定的时间限度后该方法还未被认定为热点代码,那这个方法的调用计数器的值就会减少一般,这个过程是计数器的热度衰减。
另一种回边计数器是针对循环体代码的,pc寄存器向后跳一次成为一次回边,这里统计的是实际调用次数,当达到某一阈值的时候触发OSR编译。
JVM中有两种不同的JIT compiler :C1和C2