0%

Java代码是怎么运行的

Java代码有多种不同的运行方式:在开发工具中运行,双击执行jar文件运行,在命令行中(java -jar xxx.jar)运行,在网页中(Applet)运行。这些执行方式都离不开JRE-Java运行时环境。

  • JVM,Java虚拟机
  • JRE,Java运行时环境,包括JVM和Java核心类库
  • JDK,Java开发工具表,包括JRE和一系列开发、诊断工具

Java要在虚拟机里运行

Java是一门高级程序语言,不能直接在硬件上运行,需要进行转换。通过编译器将Java程序转换成虚拟接所能识别的指令序列-Java字节码(Java字节码指令的操作码opcode被固定为一个字节)。

C++的策略是直接编译成目标架构的机器码,Java的策略是编译成一个虚拟架构的机器码(将虚拟机当做一个机器,所接收的代码格式-“机器码”。Java虚拟机的机器码-Java字节码)。

可移植性

Java虚拟机可以由硬件实现(Java processor),也可在各个现有平台上(Windows_x64、Linux_aarch64)提供软件实现(JRE)。一旦一个程序被转换成Java字节码,便可以在不同平台上的虚拟机实现里运行-一次编写,到处运行。

代码托管环境

虚拟机带来了一个托管环境-Managed Runtime。这个托管环境能够处理一些代码中冗长而且容易出错的部分,如自动内存管理、垃圾回收以及数组越界、动态类型、安全权限等动态监测。

Java虚拟机运行Java字节码

HotSpot虚拟机为例。

HotSpot是JVM中的引擎,JDK中用C++写的部分。

从虚拟机视角

从虚拟机视角,执行Java代码首先需要将它编译而成的class文件加载到Java虚拟机中。加载后的Java类会被放于方法区-Method Area中,实际运行时,虚拟机会执行方法区内的代码。(与X86的段式内存管理中的代码段类似)

Java虚拟机在内存中划分出堆(存放Java对象)和栈来存储运行时数据。并且会将栈细分为面向Java方法的Java方法栈、面向本地方法(用C++写的native方法)的本地方法栈、存放各个线程执行位置的PC寄存器。

在运行过程中,每当调用进入一个Java方法,Java虚拟机会在当前线程的Java方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。栈帧的大小是提前计算好的,并且JVM不要求栈帧在内存空间里连续分布。

当退出当前执行的方法时,不管是正常返回还是异常返回,Java虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。

从硬件视角

从硬件视角,Java字节码无法直接执行。Java虚拟机需要将字节码翻译成机器码。

在HotSpot里,有两种翻译形式:

  • 解释执行,逐条将字节码翻译成机器码并执行;
  • 即时编译(Just-In-Time compilation,JIT),将一个方法中包含的所有字节码编译成机器码后再执行(重启后需要重做)。

    AOT(ahead of time compilation)线下编译

解释执行无需等待编译,即时编译实际运行速度更快。HotSpot默认采用混合模式,综合两者优点。先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。

JVM两种编译方式:

  • 整个方法进行编译
  • 热循环进行编译-on stack replacement

Java虚拟机的运行效率

HotSpot采用多种技术提升启动性能以及峰值性能。

即时编译建立在程序符合二八定律的假设上,百分之二十的代码占据了百分之八十的计算资源。对于占据大部分的不常用的代码,才去解释执行的方式运行(多次调用多次解释执行);对于仅占据小部分的热点代码,将其编译成机器码,以达到理想的运行速度。

即时编译拥有程序的运行时信息,能够根据这个信息作出相应优化(多态-虚方法调用开销,多个目标方法只调用其中一个),执行效率可能超过静态编译。峰值性能更好。

HotSpot内置即时编译器:C1、C2、Graal(Java 10)。

  • C1,Client编译器。面向的是对启动性能有要求的客户端GUI程序,采用的优化手段相对简单,编译时间较短。
  • C2,Server编译器。面向的是对峰值性能有要求的服务端程序,采用的优化手段相对复杂,便是时间较长,但生成代码的执行效率较高。

HotSpot默认采用分层编译的方式(Java 7):热点方法首先会被C1编译,而后热点方法中的热点会进一步被C2编译(默认的分层编译,达到两千调C1,达到一万五调C2)。即时编译是放在额外的编译线程中进行的,不干扰应用的正常运行。根据CPU的数量设置编译线程的数目,按1:2的比例配置给C1及C2编译器。

热点代码:

  • 基于采样的热点探测
  • 基于计数器的热点探测
    • 方法调用计数器(JVM采用)
    • 回边计数器

字节码的解释执行和即时编译可同时执行。编译完成后的机器码会在下次调用该方法时启用,替换原本的解释执行。