Java代码是怎么运行的?_海陆云的博客-程序员宅基地_java代码如何运行

技术标签: JVM  jvm  java  


前言

Java 和 C++ 在运行方式上的区别:

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)上提供软件实现。这么做的意义在于:

  1. 一旦一个程序被转换成 Java 字节码,那么它便可以在不同平台上的虚拟机实现里运行,“一次编写,到处运行”。

  2. 带来了一个托管环境(Managed Runtime)。这个托管环境能够代替我们处理一些代码中冗长而且容易出错的部分,例如,自动内存管理与垃圾回收,这部分内容甚至催生了一波垃圾回收调优的业务。

  3. 托管环境还提供了数组越界、动态类型、安全权限等等的动态检测,免于书写这些无关业务逻辑的代码。

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

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

从虚拟机视角来看,执行 Java 代码:

首先需要将它编译而成的 class 文件加载到 Java 虚拟机中。加载后的 Java 类会被存放于方法区(Method Area)中。

实际运行时,虚拟机会执行方法区内的代码。如果熟悉 X86 的话,会发现这和段式内存管理中的代码段类似。而且,Java 虚拟机同样也在内存中划分出堆和栈来存储运行时数据。不同的是,Java 虚拟机会将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器。
在这里插入图片描述

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

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

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

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

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

HotSpot 默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。
在这里插入图片描述

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

HotSpot 采用了多种技术来提升启动性能以及峰值性能,即时编译便是其中最重要的技术之一。

即时编译建立在程序符合二八定律的假设上,也就是百分之二十的代码占据了百分之八十的计算资源。

  • 对于占据大部分的不常用的代码,无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;
  • 对于仅占据小部分的热点代码,则可以将其编译成机器码,以达到理想的运行速度。

理论上讲,即时编译后的 Java 程序的执行效率,是可能超过 C++ 程序的。这是因为与静态编译相比,即时编译拥有程序的运行时信息,并且能够根据这个信息做出相应的优化。

例如,虚方法是用来实现面向对象语言多态性的。对于一个虚方法调用,尽管它有很多个目标方法,但在实际运行过程中它可能只调用其中的一个。这个信息便可以被即时编译器所利用,来规避虚方法调用的开销,从而达到比静态编译的 C++ 程序更高的性能。

为了满足不同用户场景的需要,HotSpot 内置了多个即时编译器:C1、C2 和 Graal。Graal 是 Java 10 正式引入的实验性即时编译器。引入多个即时编译器是为了在编译时间和生成代码的执行效率之间进行取舍。

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

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

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

为了不干扰应用的正常运行,HotSpot 的即时编译是放在额外的编译线程中进行的。HotSpot 会根据 CPU 的数量设置编译线程的数目,并且按 1:2 的比例配置给 C1 及 C2 编译器。在计算资源充足的情况下,字节码的解释执行和即时编译可同时进行。编译完成后的机器码会在下次调用该方法时启用,以替换原本的解释执行。

总结

Java 之所以要在虚拟机中运行,是因为它提供了可移植性。一旦 Java 代码被编译为 Java 字节码,便可以在不同平台上的 Java 虚拟机实现上运行。

虚拟机还提供了一个代码托管的环境,代替我们处理部分冗长而且容易出错的事务,例如内存管理。

Java 虚拟机将运行时内存区域划分为五个部分,分别为方法区、堆、PC 寄存器、Java 方法栈、本地方法栈。Java 程序编译而成的 class 文件,需要先加载至方法区中,才能在 Java 虚拟机中运行。

为了提高运行效率,标准 JDK 中的 HotSpot 虚拟机采用的是一种混合执行的策略。它会解释执行 Java 字节码,然后会将其中反复执行的热点代码,以方法为单位进行即时编译,翻译成机器码后直接运行在底层硬件之上。

HotSpot 装载了多个不同的即时编译器,以便在编译时间和生成代码的执行效率之间做取舍。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38333368/article/details/122004565

智能推荐

CString、String、Char*相互转换_离水的鱼儿的博客-程序员宅基地

这三种类型各有各的优点,比如CString比较灵活,是基于MFC常用的类型,安全性也最高,但可移植性最差。string是使用STL时必不可少的类型,所以是做工程时必须熟练掌握的;char*是从学习C语言开始就已经和我们形影不离的了,有许多API都是以char*作为参数输入的。所以熟练掌握三者之间的转换十分必要。 以下我用简单的图示指出三者之间的关系,并以标号对应转换的方法。1 string to

数据库概述_阿柯@的博客-程序员宅基地

数据库的起源“数据库”起源于20世纪60年代后期,当时美国为了战争的需要,把各种情报收集在一起,存储隐藏在计算机内,叫做DataBase(DB)。1968年IBM推出层次模型的IMS系统。1969年CODASYL组织发布了DBTG报告,总结了当时各种数据库提出网状模型,成为数据库概念、方法、技术的奠基者。1970年IBM的E.F.Codd连续发表论文提出关系模型,奠定了关系数据库的理论基础。...

java 的符号集_java中的特殊字符集合_段云琦的博客-程序员宅基地

JAVA中转义字符:1.八进制转义序列:\ + 1到3位5数字;范围'\000'~'\377'\0:空字符2.Unicode转义字符:\u + 四个 十六进制数字;0~65535\u0000:空字符3. 特殊字符:就3个\': 双引号\': 单引号\\:反斜线4. 控制字符:5个\' 单引号字符\\ 反斜杠字符\r 回车\n 换行\f 走纸换页\t 横向跳格\b 退格点的转义:. ==> u...

【机器学习课程-华盛顿大学】:1 案例研究 1.5 推荐系统(1)概念描述_有石为玉的博客-程序员宅基地

一、推荐系统1、推荐系统简述结合你和别人的历史购物信息,给你推荐合适的商品。 2、推荐系统应用场景:(1)youtube:解决信息过载问题 (2)电影推荐 (3)商品推荐兴趣、推荐还会随着时间的变化而变化,比如宝宝在不断成长,需要的东西也在不断变化。 (4)音乐推荐 (5)朋友推荐3、搭建推荐系统(1)最简单的方法...

react——项目搭建(引入 typescript)、基础数据事件传输绑定(TodoList)_肖ZE的博客-程序员宅基地_typescript事件绑定

import React, { Component, Fragment } from 'react'import './TodoList.css'class TodoList extends Component { constructor(props) { super(props) this.state = { inputVal...

【C语言刷LeetCode】997. 找到小镇的法官(E)_kinbo88的博客-程序员宅基地

【在一个小镇里,按从 1 到 N 标记了 N 个人。传言称,这些人中有一个是小镇上的秘密法官。如果小镇的法官真的存在,那么: 小镇的法官不相信任何人。 每个人(除了小镇法官外)都信任小镇的法官。 只有一个人同时满足属性 1 和属性 2 。给定数组 trust,该数组由信任对 trust[i] = [a, b] 组成,表示标记为 a 的人信任标记为 b 的人...

随便推点

2021年一建不可错过的备考技巧!_正鸣教育的博客-程序员宅基地

​一级建造工程师的难度是大家有目共睹的,除了努力备考,方法也很重要。对于学习效率比较低和基础比较差的朋友们,小编整理了已通关者的经验和心得供大家参考,希望对大家有所启发。​做好复习的规划制定好计划,你就知道力气该往什么地方使了,漫无目的的备考只会拖住自己的脚步,严重者可能会在备考中迷失。规划好每一个科目的复习时间正在备考的朋友们大多是上班族,不同的知识点可以安排在不同的时间阶段,在空闲时间比较长的情况下可以复习比较重要的知识点,在空闲比较短的情况下可以复习不太重要的知识点。培养自律的习惯如果是

C程序设计语言(K&R 第二版):练习1-1_哲思天下的博客-程序员宅基地_c程序设计语言第二版习题

题目为:在你自己的系统中运行“hello world”程序。再有意去掉程序中的部分内容,看看会得到什么错误信息。源代码如下:#include <stdio.h>int main(){ printf("Hello World\n"); return 0;}采用在线C编译器编译:c在线编译器,c语言在线解释器,在线编程网站情形1: 去掉printf语句之后的“;”,报出的编译错误如下:Compilation Failed/usercod...

计算机网络发现展,计算机网络拓扑发现系统的研究与实现_想要未知的疯狂的博客-程序员宅基地

摘要:计算机网络发展迅速,人们对计算机网络的高度依赖性使得网络运行的可靠性变得至关重要,因此也就对网络管理提出了更高的要求.在网络管理中,网络拓扑结构信息是网络管理其他所有管理功能的基础,只有掌握了网络正确,完整的拓扑结构,才能对其实施行之有效的管理措施.因此,如何快速,高效并完整地获取计算机网络的拓扑结构信息是当前网络管理领域关注的重要问题.网络拓扑是网络节点分布及其连接关系的快照.本文分层进行...

用netty实现zcool_netty的简单的应用例子_何羽菲的博客-程序员宅基地

一、简单的聊天室程序public classChatClient {public static void main(String[] args) throwsInterruptedException, IOException {NioEventLoopGroup nioEventLoopGroup= newNioEventLoopGroup();try{Bootstrap bootstrap= n...

Kubernetes v1.9.1 单机版本一键安装脚本_weixin_33895516的博客-程序员宅基地

#!/bin/bash# ----------------------------------------# kubernetes v1.9.1 单机一键部署脚本# 用于实验环境# CentOS 7.2.1511下测试OK# Powered by Jerry Wong# 2018-03-15 [email protected]# ----------------------------...

数据结构之栈 篇二——自定义数据类型传入实现(以Coordinate坐标类为例)_哟米 2000的博客-程序员宅基地

单一数据类型栈篇已经写过了,介绍int单一型传入,其他单一型方法类似本篇记载较复杂类型,定义Cooridinate类,传入坐标值,再熟悉栈的功能代码在篇一基础上添加了新的Coordinate类定义,和原来代码简单修改。为了方便学习和调试,这里还是把全部代码复制过来。1、Coordinate.h#ifndef COORDINATE_H#define COORDINATE_H...