一行 Java 代码是怎么执行的?

在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两个阶段,第一阶段是把.java文件转换成.class文件。第二阶段是把.class转换成机器指令的过程。
简言之:
第一阶段.java文件由Java前端编译器(JVM的javac或者Eclipse JDT中的增量式编译器)编译成字节码;
第二阶段:两种方式:
方式一:字节码文件被JVM的Java解释器先“解释”成汇编指令,再“解释”成真实物理CPU可以识别的机器指令。特别强调,解释器是软件实现的,不同的硬件平台上的解释器可能不一样。
方式二:JVM为了提高运行效率,会将某些热点代码块"一次全部编译"成机器指令后再执行,也就是即时编译JIT。

相关定义:

Java编译器:JVM的一部分,将Java源文件(.java文件)编译成字节码文件(.class文件,是特殊的二进制文件,二进制字节码文件),这种字节码就是JVM的“机器语言”。javac.exe可以简单看成是Java编译器,和JIT编译器不是同一回事。

Java解释器:JVM的一部分。Java解释器先将字节码解释成汇编代码,然后将汇编代码解释成机器指令(汇编代码中的每一个助记符其实对应一个机器码,助记符只是方便开发人员理解和记忆。)解释器不会一次性把整个程序翻译出来,每翻译一行程序叙述就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去,所以它的速度慢。java.exe可以简单看成是Java解释器。
例子:
在cmd中,执行java命令与javac命令的区别:
javac:是编译命令,将java源文件编译成.class字节码文件。
例如:javac hello.java
将生成hello.class文件。
java:是运行字节码文件;由java虚拟机对字节码进行解释和运行。
例如:java hello
注意:通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作,因为此可执行文件包含了对目标处理器的机器语言。而Class文件这种特殊的二进制文件,是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的!

即时编译(Just-in-time compilation: JIT):又叫实时编译、及时编译。JIT编译器是JRE的一部分。它的产生背景是Java解释器执行的效率过低,为了提高执行效率,便引入了JIT。在运行时,JIT会对经常执行的热点代码一次性地全部编译成机器码,然后直接运行编译后的机器码,是一种在运行时期就能把字节码编译成原生机器码的技术。它的工作方式是先是一句一句翻译源代码,然后将翻译过的代码汇总缓存起来直接以降低性能耗损,还可以备下次使用。这项技术被用来改善虚拟机的性能。参考:https://baike.baidu.com/item/%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E5%99%A8/18428531?fr=aladdin
译码器:译码器实际上是由许多与门、或门、非门和它们的组合构成的。它有若干个输入端和若干个输出端(也可能只有一个输出端)。对某一个输出端来说,它的电平高低必然与输入的某一种状态相对应。例如,具有4个输入端的与非门就是一个简单的译码器,只有四个输入端为1111时,它的输出端才为0。
二进制文件:广义的二进制文件即为文件,由文件在外部存储设备的存放方式为二进制而得名。狭义的二进制文件即指除文本文件以外的文件。文本文件的格式包括:ASCII、MIME、txt。

字节码:是特殊的二进制文件,字节码是已经经过编译,但与特定机器码无关,需要解释器转译后才能成为机器码的中间代码。

Java字节码:好比是JVM的“机器指令码”。之所以称为字节码是因为 Java 字节码的操作指令(OpCode)被固定为一个字节。

以 System.out.println(“Hello world”) 来解释一行 Java 代码是怎么执行的?

System.out.println(“Hello world”) 编译后的字节码,如下:
0x00: b2 00 02 getstatic Java .lang.System.out
0x03: 12 03 ldc “Hello, World!”
0x05: b6 00 04 invokevirtual Java .io.PrintStream.println
0x08: b1 return
其中,最左列是偏移;中间列是给虚拟机读的字节码;最右列是高级语言的代码。中间列的第一个是指令码,最右列的第一个是助记符,二者是一一对应的关系,如b2对应getstatic ,12对应ldc,这个便是JVM指令集。参考:https://blog.csdn.net/u012070360/article/details/81624854

System.out.println(“Hello world”) 对应的汇编语言转换成的机器指令,如下:
0x00: 55 push rbp
0x01: 48 89 e5 mov rbp,rsp
0x04: 48 83 ec 10 sub rsp,0x10
0x08: 48 8d 3d 3b 00 00 00 lea rdi,[rip+0x3b]
; 加载 “Hello, World!\n”
0x0f: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
0x16: b0 00 mov al,0x0
0x18: e8 0d 00 00 00 call 0x12
; 调用 printf 方法
0x1d: 31 c9 xor ecx,ecx
0x1f: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
0x22: 89 c8 mov eax,ecx
0x24: 48 83 c4 10 add rsp,0x10
0x28: 5d pop rbp
0x29: c3 ret
其中,第一列是偏移量,中间是机器指令,最后一列是对应的汇编代码。

总结:
从.java文件到java字节码文件的工作是由java编译器完成的。
从java字节码文件到汇编指令再到机器指令则是由java解释器完成的。
从机器指令到高低电平是由真实物理cpu中的译码器完成的。
如果是即时编译JIT,则将字节码文件直接编译成最终机器指令。
java代码->java字节码->汇编代码->机器码->高低电平,这就是.java文件到最终的高低电平的过程。
此外,Java中存在大量用native标识的本地方法,这些本地方法充当接口的角色。通过使用本地方法,我们得以用Java实现jre与底层系统的交互,甚至JVM的一些部分就是用C语言写的。还有,如果我们要使用一些Java语言本身没有提供的操作系统的特性时,我们也需要使用本地方法。
**参考:
https://www.freesion.com/article/46071099221/
https://blog.csdn.net/coolbear1027/article/details/16838575
https://www.freesion.com/article/46071099221/
https://www.zhihu.com/question/65385471/answer/983476358

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值