Java 代码有很多种不同的运行方式。比如说,可以在开发工具中运行,可以双击执行 jar 文件运行,也可以在命令行中运行,甚至可以在网页中运行。当然,这些执行方式都离不开 JRE,也就是 Java 运行时环境。
JRE 仅包含运行 Java 程序的必需组件,包括 Java 虚拟机以及 Java 核心类库等。

为什么 Java 要在虚拟机里运行?

  1. Java 代码复杂需要转换后才能在硬件上运行,设计一个面向 Java 语言特性的虚拟机,不同系统不同虚拟机,做到一次编译到处运行;
  2. 虚拟机能够提供一个托管环境,替代处理一些代码中冗余容易出错的处理(内存管理、垃圾回收等);

虚拟机的用处:

  1. 在不同的平台上运行字节码,即一次编写到处运行;
  2. 提供一个托管环境,能够处理代码中冗长且容易出错的部分,内存管理与垃圾回收;
  3. 处理非业务代码:如数组越界、动态类型、安全权限等动态检测;

Java 虚拟机具体是怎样运行 Java 字节码的?

1) 加载 Java 编辑成的 class 文件到虚拟机,加载好后放到方法区(Method Area);
2) 执行时需要将字节码翻译成机器码, 有【解释执行】和【即时编译】(JIT) 两种,线程共享:方法区、堆;线程私有:PC寄存器、Java方法栈、本地方法栈;
image.png
【解释执行】:逐条将字节码翻译成机器码并执行,优势:无需等待编译;
【即时编译】:将一个方法中包含的所有字节码编译成机器码后再执行。优势:实际运行速度更快;
HotSpot 默认采用混合模式,先解释执行字节码,然后将其中反复执行的热点代码,以方法为单位进行即时编译;

Java 虚拟机的运行效率究竟是怎样的?

判断一段代码是否是热点代码有两种方法:1) 基于采样的热点探测;2) 基于计数器的热点探测
采用第二种方法的虚拟机会为每个方法建立**计数器**, 用于统计方法的执行次数,如果执行次数超过一定的阈值就认为它是热点方法。
方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行效率,即一段时间内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器,那这个方法的调用计数器就会被减少一半,这个过程被称为方法调用计数器热度的衰减,而这段时间就称为方法统计的半衰周期,进行热度衰减的动作在虚拟机进行垃圾收集时顺便进行了。

HotSpot 内置多个即时编译器:C1C2Graal
C1:又叫做 Client 编译器,面向对启动性能有要求的客户端 GUI程序,采用的优化手段相对简单,编译时间较短;
C2:又叫做 Server 编译器,面向对峰值性能有要求的服务器端程序,采用的优化手段相对复杂,编译时间较长,但同时生成代码的执行效率较高;

从 Java7 开始,HotSpot 默认采用分层编译的方式:热点方法首先会被 C1 编译,而后热点方法中的热点会进一步被 C2 编译;
HotSpot会根据 CPU 的数量设置编译线程的数目,并且按 1 : 2 的比例配置给 C1 及 C2 编译器。