计算机为何可以运行Java代码?(上)

简介: 计算机为何可以运行Java代码?

Java代码有很多种不同的运行方式。比如说可以在开发工具中运行,可以双击执行jar文件运行,也可以在命令行中运行,甚至可以在网页。

这些执行方式都离不开JRE,Java运行时环境。


JRE仅包含运行Java程序的必需组件,包括Java虚拟机以及Java核心类库等。我们Java程序员经常接触到的JDK(Java开发工具包)同样包含了JRE,并且还附带了一系列开发、诊断工具。


然而,运行C++代码则无需额外的运行时。往往把这些代码直接编译成CPU所能理解的代码格式,即机器码。


比如下图的中间列,就是用C语言写的Helloworld程序的编译结果。

C程序编译而成的机器码就是一个个的字节,它们是给机器读的。那为让开发人员也能理解,用反汇编器将其转换成汇编代码(如下图的最右列所示)。

; 最左列是偏移;中间列是给机器读的机器码;最右列是给人读的汇编代码
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程序转换成该虚拟机所能识别的指令序列,即Java字节码。

之所以这么取名,是因为Java字节码指令的操作码(opcode)被固定为一个字节。


下图的中间列,正是用Java写的Helloworld程序编译而成的字节码。可以看到,它与C版本的编译结果一样,都是由一个个字节组成的。

同样可以将其反汇编为人类可读的代码格式(如下图的最右列所示)。

Java版本的编译结果相对精简一些,Java虚拟机相对于物理机而言,抽象程度更高。


/

# 最左列是偏移;中间列是给虚拟机读的机器码;最右列是给人读的代码
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

Java虚拟机常见的是在各个现有平台(如Windows_x64、Linux_aarch64)上提供软件实现。一旦一个程序被转换成Java字节码,便可在不同平台上的虚拟机实现里运行,即“一次编写,到处运行”。


虚拟机的另外一个好处是它带来托管环境(Managed Runtime),代替我们处理一些代码中冗长而且容易出错的部分。自动内存管理与垃圾回收,这部分内容甚至催生了一波垃圾回收调优。


托管环境还提供了诸如数组越界、动态类型、安全权限等等的动态检测。

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

以标准JDK中的HotSpot虚拟机为例,从虚拟机以及底层硬件两个角度,给你讲一讲Java虚拟机具体是怎么运行Java字节码的。


虚拟机视角,执行Java代码首先要将它编译而成的class文件加载到Java虚拟机。

加载后的Java类会被存放于方法区(Method Area)。实际运行时,虚拟机会执行方法区内的代码。


这和段式内存管理中的代码段类似。而且,Java虚拟机同样也在内存中划分出堆和栈来存储运行时数据。


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


image.png

image.png

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


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


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

HotSpot翻译过程有两种形式:

  • 解释执行,逐条将字节码翻译成机器码并执行
    无需等待编译
  • 即时编译(Just-In-Time compilation,JIT),将一个方法中包含的所有字节码编译成机器码后再执行
    实际运行速度更快

image.png

HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点:

先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。

目录
相关文章
|
5天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
17 5
Java反射机制:解锁代码的无限可能
|
1天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
17 3
|
7天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
33 10
|
2天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
1天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
9天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
19 6
|
9天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
10天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
31 3
|
10天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
20 1
|
12天前
|
Java
Java代码解释静态代理和动态代理的区别
### 静态代理与动态代理简介 **静态代理**:代理类在编译时已确定,目标对象和代理对象都实现同一接口。代理类包含对目标对象的引用,并在调用方法时添加额外操作。 **动态代理**:利用Java反射机制在运行时生成代理类,更加灵活。通过`Proxy`类和`InvocationHandler`接口实现,无需提前知道接口的具体实现细节。 示例代码展示了两种代理方式的实现,静态代理需要手动创建代理对象,而动态代理通过反射机制自动创建。