Java代码是怎么运行的

Java为什么要运行在虚拟机上

Java作为一门高级程序语言,它的语法非常复杂,抽象成都也很高。因此,直接在硬件上运行这种复杂的程序并不现实,因此在运行Java程序之前,我们需要对其进行一番转换。

转换的思路:设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换成该虚拟机所能识别的指令序列,也称Java字节码。字节码之所以取名为字节码,是因为Java字节码指令的操作码(opcode)被固定为一个字节。

Java虚拟机是怎么运行Java字节码的

从虚拟机角度看待这个问题

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

Java虚拟机会将栈细分为面向Java方法的Java方法栈,面向本地方法(用C++写的native方法)的本地方法栈,以及存放各个线程执行位置的PC寄存器。

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

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

从硬件角度看待这个问题

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

在HotSpot里面,上述翻译过程有两种形式:

第一种是解释执行,即逐条将字节码翻译成机器码并执行;

第二种是即时编译(Just-In-Time,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。

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

HotSpot负责编译的编译器

HotSpot内置了多个即时编译器:C1、C2和Graal

HotSpot之所以引入多个即时编译器,是为了在编译时间和生成代码的执行效率之间进行取舍。

C1又叫做Client编译器,面向的是对启动性能有要求的客户端GUI程序,采用的优化手段相对简单,因此编译时间较短。

C2又叫做Server编译器,面向的是对峰值性能有要求的服务器端程序,采用的优化手段相对复杂,因此编译时间较长,但同时生成代码的执行效率较高。

从Java7开始,HotSpot默认采用分层编译的方式:热点方法首先会被C1编译,而后热点方法中的热点会进一步被C2编译。

通过ASM修改字节码来理解JVM中的boolean是以什么方式存在的

// 生成 Java 文件
$ echo '
public class Foo {
 public static void main(String[] args) {
  boolean flag = true;
  if (flag) System.out.println("Hello, Java!");
  if (flag == true) System.out.println("Hello, JVM!");
 }
}' > Foo.java
// 编译
$ javac Foo.java
// 运行
$ java Foo
// java -cp 依赖 asmtools.jar 使用jdis将Foo.class转成Foo.jasm.1
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.1
// 使用awk修改Foo.jasm.1文件并生成Foo.jasm
$ awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.1 > Foo.jasm
// java -cp 依赖 asmtools.jar 使用jasm编译Foo.jasm生成Foo.class文件
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm
// 执行新生成的Foo.class文件
$ java Foo

Java -cp用法介绍:

java -cp 和 -classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要全路径到jar包,window上分号“;”

分隔,linux上是分号“:”分隔。不支持通配符,需要列出所有jar包,用一点“.”代表当前路径。

运行结果

第一次执行:
Hello, Java!
Hello, JVM!
修改字节码后执行:
Hello, Java!

运行分析,借用评论区第一大佬的回答

jvm把boolean当做int来处理

flag = iconst_1 = true

awk把stackframe中的flag改为iconst_2

if(flag)比较时ifeq指令做是否为零判断,常数2仍为true,打印输出

if(true == flag)比较时if_cmpne做整数比较,iconst_1是否等于flag,比较失败,不再打印输出