JVM 运行 Java 字节码

从 JVM 来看,执行 Java 程序需要将编译后的 class 文件加载到虚拟机中,加载完成后,Java 类会被放到方法区中,运行时,JVM 就会执行方法区中的代码。

JVM 在内存中划分出堆和栈来保存运行时产生的数据,JVM 在划分栈时,会细分为面向 JAVA 的方法栈和面向本地的本地方法栈(用 C++ 写的 native 方法)。

JVM-内存划分

当调用一个方法时,JVM 会在 Java 方法栈中生成一个栈帧用于存放方法的运行时数据,而且栈帧的大小是提前计算好的,JVM 并不要求栈帧在内存地址是连续的。

HotSpot

JVM 中的 HotSpot 将字节码翻译成机器码有两种方法:

  1. 解释执行:逐条翻译字节码,逐条执行,无需等待编译,直接执行;
  2. 即时编译(Just-In-Time compilation,就是常说的 JIT):将一个方法中的字节码全部翻译完成后执行,需要等待编译,但实际运行速度更快;

JVM-解释执行-即时编译

HotSpot 结合了两种方法,先执行字节码,对于反复调用的热点代码,以方法为单位进行即时编译。即时编译可以得到代码在运行时的信息,且可以根据这个信息做出相应的优化,在一定程度上是有可能超过 C++ 的速度的。

HotSpot 内置了多个即时编译器:

  • C1:也叫 Client 编译器,面向的是对启动性能有要求的 GUI 程序,优化手段简单,编译时间短;
  • C2:也叫 Server 编译器,面向的是峰值性能有较高要求的服务器端程序,优化手段复杂,编译时间长,但生成的字节码执行效率高;

Java7 后 HotSpot 采用混合编译,先使用 C1 对热点方法进行编译,对于后热点方法会进一步被 C2 编译。

即时编译为了不影响应用的正常运行,是放在额外的编译线程中运行的(CPU数量 * 线程数 = 可利用资源,极限情况下:可利用资源 - 1 = 编译线程),按照 1:2 分配给 C1 和 C2。资源充足的情况下,解释执行和即时编译同步进行,编译完成后,在下一次调用相同的方法时,会用即时编译的结果替换解释执行。

收集到的问题

  1. 为什么不把java代码全部编译成机器码?很多服务端应用发布频率不会太频繁,但是对运行时的性能和吞吐量要求较高。如果发布或启动时多花点时间编译,能够带来运行时的持久性能收益,不是很合适么?

    答:因为对于发布不频繁的应用,使用线下编译和即时编译差别并不大,在经过一段时间后,也会对所有代码编译完成,而且即时编译会收集运行过程中的信息,对于优化有一定的帮助,换句话说,即时编译后能达到的峰值性能更高