Java虚拟机:虚拟机介绍

大家好,我是栗筝i,这篇文章是我的 "栗筝i 的 Java 技术栈" 专栏的第 033 篇文章,在 "栗筝i 的 Java 技术栈" 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。

--

在当今的软件开发领域,Java 虚拟机(JVM)扮演着至关重要的角色,它不仅是 Java 语言的运行时环境,也是跨平台应用的基石。理解 JVM 的原理和功能,对 Java 开发者来说至关重要。本篇文章将带你全面了解 JVM 的起源、结构和工作原理,帮助你奠定扎实的基础,为后续深入研究 JVM 的各个方面做好准备。


文章目录

      • [1、对于 Java 虚拟机的认知](#1、对于 Java 虚拟机的认知)
        • [1.1、Java 虚拟机(Jvm) 与 Java 语言的关系](#1.1、Java 虚拟机(Jvm) 与 Java 语言的关系)
        • [1.2、one write run anywhere(一次编写到处运行)](#1.2、one write run anywhere(一次编写到处运行))
          • [1.2.1、C 语言的编译过程](#1.2.1、C 语言的编译过程)
          • 1.2.2、如何理解汇编语言
          • [1.2.3、C 语言为什么不能跨平台](#1.2.3、C 语言为什么不能跨平台)
          • [1.2.4、Java 为什么可以跨平台](#1.2.4、Java 为什么可以跨平台)
      • [2、Java 虚拟机的基础知识](#2、Java 虚拟机的基础知识)
        • [2.1、Java 虚拟机概述](#2.1、Java 虚拟机概述)
        • 2.2、JVM、JRE、JDK的关系
        • [2.3、Java 虚拟机的结构组成](#2.3、Java 虚拟机的结构组成)

1、对于 Java 虚拟机的认知

1.1、Java 虚拟机(Jvm) 与 Java 语言的关系

在我们系统的认识 Java 虚拟机前,我们应该明确一个事实,即:++"Java 虚拟机不和包括 Java 在内的任何一种语言绑定,它只与 'Class文件' 这种特定的二进制文件格式所关联,Class 文件中包含了 Java 虚拟机指令集和符号表以及若干其他辅助信息"++

所以 "Java 虚拟机" 这一命名本身有一定的误导性,Java 虚拟机和 Java 语言并不存在必要的联系,将其称作 "Class 文件虚拟机" 可以更适合理解。

Java 在 1997 年发布规范文档的时候,也刻意的将 Java 的规范拆分为《 Java 语言规范(The Java Language Specification)》与 《Java 虚拟机规范(The Java Virtual Machine Specification)》两部分。

Java 语言规范 & Java 虚拟机规范:https://docs.oracle.com/javase/specs/

Java 虚拟机是语言无关性的,作为一个通用的、与机器无关的执行平台,任何其他语言的实现都可以将 Java 虚拟机作为其运行的基础,以 Class 文件作为他们产品的交付媒介。

例如,使用 Java 编译器可以把 Java 代码编译为存储字节码的 Class 文件,使用 JRuby 等其他语言的编译器一样可以把它们的源程序代码编译成 Class 文件。虚拟机丝毫不关心 Class 的来源是什么语言,它与程序语言之间的关系如图所示:

1.2、one write run anywhere(一次编写到处运行)

在我们初次接触 Java 时,都不可不避免的了解到,Java 的一个优势,也是 Java 刚推出时的宣传语:"one write run anywhere(一次编写到处运行)",在这句话中,突出了 Java 虚拟机核心的思想,Java 虚拟机是与平台无关的,或者说 Java 虚拟机具有平台无关性!

1.2.1、C 语言的编译过程

既然 Java 虚拟机具有平台无关性,那么什么是平台有关性呢,我们先来看一下,C 语言的执行过程:

c 复制代码
#include <stdio.h>
int main()
{
    printf("hello world");
    return 0;
}

这是一个 C 语言的程序,最终的至执行结果是打印一行 "hello world" 字符串,我们知道,计算机里的世界里只有 "0" 和 "1",那以上的 C 程序是如何转换为 "0" 和 "1" 的呢?

以上 C 程序的编译过程:

我们分阶段来讨论:

  • 首先是 "预处理阶段" :预处理器(cpp)会将头文件(# 开头的行)展开,将宏名替换为字符串,并将注释去掉;
  • 接下来是 "编译阶段" :编译器(ccl)将修改后的 .c 文件,翻译为 .s 文件,即汇编程序;
  • 再然后是 "汇编阶段" :汇编器(as)将 .s 文件翻译成 .o 文件,这就是机器语言指令,二进制文件;
  • 在最后是 "链接阶段" :链接器(ld)将 .o 文件中的函数库对应的代码组合到最终目变文件中,这就是 .out 文件了。
1.2.2、如何理解汇编语言

作为一个插曲,在这里阐述一下笔者对于汇编语言的理解。

什么是汇编语言,其实就是因为计算机只能识得 "0" 和 "1",在计算机刚被发明时,科学家们只能通过向计算机输入 0|1 代码的方式(通过穿孔纸带,有孔 1,无孔 0),来运行计算任务,但是这样的效率实在太慢了,所以汇编语言就被发明出来,用于简化程序的编写。

比如计算 1+1,两个 "1" 数据使用 0x0001 来表示,而 "加" 操作,放在 CPU 中,可以是 0xa90df(这个是胡乱写的),这个二进制代表的加操作能被计算机识别。而因为这个加操作对于 CPU 来说,编码的 0xa90df 格式是固定的。所以可以直接一个助记符 add 来表示,这样科学家们写程序就方便多了。而这就是汇编程序的由来。因为汇编程序完成之后,可以再有一个专门的程序(就是要上文中所说的汇编器)来把编写的汇编程序编译成 "0" 和 "1",这样计算机也可以识别了,而汇编语言本身也方便了程序的编写和阅读。

编写汇编比直接编写二进制方便高效了太多。但是随着计算任务的复杂,程序的规模越来越庞大,使用汇编程序也很累啊,那么是否有更简单的方式呢?所以科学家们发明了高级语言(比如 C,lisp 等),在编写程序的时候,使用 C。语言等编写,然后再使用编译器将 C 语言程序翻译成汇编程序,汇编程序再使用汇编器编译成 "0" 和 "1",这样,CPU 能识别的东西没有变化,但是对于编写程序的人,确实方便了很多。

通过以上的描述,我们就知道了高级语言的大概由来。也明白了我们所编写的各种高级语言,到了最后,其实都是转化为二进制执行。

1.2.3、C 语言为什么不能跨平台

在对汇编语言再一次理解之后,我们回到正题,即为什么 C 语言不具备平台无关性,或者说为什么 C 语言为什么不能跨平台。

直接二进制格式的程序,我们称之为本地机器码(native code)。而类似那些 add 之类的助记符,以及汇编的编写格式或标准,我们称之为指令集。

但是问题的关键来了。不同公司所生产的 CPU 芯片。他们所使用的指令集不同啊,这种芯片设计的事情,又不像 TCP/IP 协议那样,有国际统一的标准,甚至像 Intel 所代表的复杂指令集,和 ARM 为代表的精简指令集,它们指令集的设计思路就是不一样的。

所以我们 C 语言最后编译出来的的二进制文件,不同的 CPU 上识别的意义是完全不同的。

此外,还有一种场景,就是一段 C 语言的程序,我们编写一个 .c 的文件后,将其拿到另外一个操作系统|CPU 平台去执行,是否会成功呢?成功了之后那么是否可以说明 C 语言具有跨平台性呢,很多人存在着不同的看法,对此,笔者是这样认为的:

  1. C 语言并非不能跨平台(CPU 或者操作系统),准确的说是 C 语言编译出的程序无法二进制跨平台;
  2. 不同环境下,C 语言的标准有差别,比如不同平台的 int 类型可能是 16 位表示也可能是 32 位表示,但笔者认为这种情况是可以有多种方式去规避的,随意最终的结论还是 C 语言不能跨平台是特指二进制跨平台。

那有没有一种办法,能够让高级语言实现(二进制)跨平台的运行呢?

思考实际编程中的一个场景,我们前端需要处理的某个数据是 A 格式,但是后台只能提供B格式的数据,那我们怎么办?很简单啊。写个接口,把 B。格式转化为 A 格式。这就是设计模式当中的适配器设计模式。

关于跨平台也是一样的道理。CPU 的指令集不同, 不同平台编译出来的结果格式都不同,那么我们可以在各个平台上运行虚拟机,然后我们制定某种编译结果的输出格式,我们的输出了某种格式的结果,直接在虚拟机上运行。 这就是 Java 采取的方式。

1.2.4、Java 为什么可以跨平台

这是 Java 版本的 "Hello World":

java 复制代码
public static void main(String[] args) {
    System.out.println("Hello World!");
}

这段 Java 程序编译出来的结果是 hello.class,换句话说,它输出的结果是 Class 文件格式(也叫字节码存储格式)。

这其实就是 Java 虚拟机定义的二进制格式,这种我们称之为字节码(ByteCode),是 Java 虚拟机所能运行的格式。类似本地机器码可以反编译成汇编,这种二进制也可以反编译成更容易阅读的格式。

而各个平台的 Java 虚拟机 是不同的。但是我们编写的 Java 程序 统一编译成特定格式的 Class 文件格式,然后 Class 文件可以在各个不同平台的 Java 虚拟机上运行,当然运行结果肯定也是一致的,至于各个不同平台之间的差异,这是那些编写 Java 虚拟机的人去考虑的事情。

通过这种方式,我们的 Java 程序就实现了跨平台。


2、Java 虚拟机的基础知识

2.1、Java 虚拟机概述

Java 虚拟机(英语:Java Virtual Machine,缩写:JVM),一种能够执行 Java 字节码(Class 文件)的虚拟机,以堆栈结构机器来进行实做。最早由 Sun 公司所研发并实现第一个实现版本,是 Java 平台的一部分,能够执行以 Java 语言所写的软件程序。

Java 虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过对中央处理器(CPU)所执行的软件实现,实现能执行编译过的 Java 程序码(Applet 与应用程序)。

作为一种编程语言的虚拟机,实际上不只是专用于 Java 语言,只要生成的编译文件符合 JVM 对加载编译文件格式要求,任何语言都可以由 JVM 编译运行。此外,除了甲骨文,也有其他开源或闭源的实现。

2.2、JVM、JRE、JDK的关系

JVM 是 Java 程序能够运行的核心。但需要注意,JVM 自己什么也干不了,你需要给它提供生产原料(Class 文件)。 仅仅是 JVM,是无法完成一次编译,处处运行的。它需要一个基本的类库,比如怎么操作文件、怎么连接网络等。

而 Java 体系很慷慨,会一次性将 JVM 运行所需的类库都传递给它。JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE。

对于 JDK 来说,就更庞大了一些。除了JRE,JDK还提供了一些非常好用的小工具,比如 javac、java、jar 等。它是 Java 开发的核心。 我们也可以看下 JDK 的全拼,JavaDevelopmentKit。

JVM、JRE、JDK 它们三者之间的关系,可以用一个包含关系表示:

2.3、Java 虚拟机的结构组成

Java 虚拟机整体组成可分为四个部分:类加载器(Class Loader)、运行时数据区(Runtime Data Area)、执行引擎(Execution Engine)、本地库接口(Native Interface)

  • 类加载器:负责从字节码 Class 文件中,加载 Class 信息到运行时数据区的方法区;
  • 运行时数据区:存放 Jvm 在执行 Java 程序时相关数据的区域;
  • 执行引擎:将字节码翻译成底层系统指令再交由 CPU 去执行;
  • 本地库接口:执行过程中可能需要调用到其他语言(比如 C 语言)的本地接口。

Ps:Javac 是收录于 Jdk 中的 Java 语言编译器。该工具可以将 .java 源文件编译为 Class 文件(.class 文件)。

在程序被执行之前,Java 前, Java 代码会被先转换成字节码 Class 文件,Jvm 首先通过一定的方式使用「类加载器」把字节码文件加载到内存中「运行时数据区」,而字节码文件是 Jvm 提供的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器「执行引擎」将字节码翻译成底层系统指令再交由 CPU 去执行,而这个过程中需要调用其他语言的接口「本地库接口」,来实现整个程序的功能,这就是这 4 个主要组成部分的职责与功能。

相关推荐
栗筝i1 个月前
Java虚拟机:类的加载机制
栗筝i 的 java 技术栈·java 基础·java 虚拟机
栗筝i1 个月前
Java虚拟机:运行时内存结构
栗筝i 的 java 技术栈·java 基础·java 虚拟机
栗筝i1 个月前
Java 并发编程:线程变量 ThreadLocal
threadlocal·栗筝i 的 java 技术栈·java 基础·java 并发·线程变量
栗筝i1 个月前
Java 并发编程:volatile 关键字介绍与使用
内存屏障·栗筝i 的 java 技术栈·java 基础·java 并发·volatile 关键字
栗筝i2 个月前
Java 并发编程:Java 线程池的介绍与使用
java基础·栗筝i 的 java 技术栈·java 并发·java 线程池
栗筝i2 个月前
Java 集合框架:TreeMap 的介绍、使用、原理与源码解析
java基础·r-tree·java集合·treemap·栗筝i 的 java 技术栈
栗筝i2 个月前
Java 集合框架:Java 中的优先级队列 PriorityQueue 的实现
优先级队列·栗筝i 的 java 技术栈·java 基础·java 集合·java 堆排序
栗筝i2 个月前
Java 并发集合:CopyOnWrite 写时复制集合介绍
栗筝i 的 java 技术栈·java 基础·java 集合
威哥爱编程3 个月前
使用explain优化慢查询的业务场景分析
数据库·sql·java 基础