
🌸雨落在了我的手上:个人主页
🐟个人仓库:Gitee仓库
❄️个人专栏:<<JaveSe>> <<C语言>> <<C语言数据结构>> <<Java数据结构*** >>***
<<MySQL数据库基础 >> <<JavaEE初阶>>
🔥🔥🔥人生格言:无人扶我青云志,我自踏雪至山巅
🎬 博主简介

目录
[2.1 为什么人类需要计算机?](#2.1 为什么人类需要计算机?)
[2.2 计算机究竟是什么?](#2.2 计算机究竟是什么?)
[3.1 计算机的五类基本组成](#3.1 计算机的五类基本组成)
[3.2 计算机的工作过程](#3.2 计算机的工作过程)
[3.3 CPU、内存和硬盘有什么区别?](#3.3 CPU、内存和硬盘有什么区别?)
[四、从 0 和 1 开始:计算机为什么使用二进制?](#四、从 0 和 1 开始:计算机为什么使用二进制?)
[4.1 什么是二进制?](#4.1 什么是二进制?)
[4.2 什么是 bit 和 byte?](#4.2 什么是 bit 和 byte?)
[4.3 二进制如何表示十进制数字?](#4.3 二进制如何表示十进制数字?)
[5.1 从电子开关说起](#5.1 从电子开关说起)
[5.2 什么是逻辑门?](#5.2 什么是逻辑门?)
[5.3 非门(NOT)](#5.3 非门(NOT))
[5.4 与门(AND)](#5.4 与门(AND))
[5.5 或门(OR)](#5.5 或门(OR))
[5.6 异或门(XOR)](#5.6 异或门(XOR))
[5.7 为什么逻辑门如此重要?](#5.7 为什么逻辑门如此重要?)
[六、ALU:CPU 中负责运算的核心部件](#六、ALU:CPU 中负责运算的核心部件)
[6.1 什么是 ALU?](#6.1 什么是 ALU?)
[6.2 ALU 是如何实现加法的?](#6.2 ALU 是如何实现加法的?)
[6.3 ALU 和 Java 中的加法有什么关系?](#6.3 ALU 和 Java 中的加法有什么关系?)
[7.1 什么是寄存器?](#7.1 什么是寄存器?)
[7.2 什么是 RAM?](#7.2 什么是 RAM?)
[7.3 为什么内存很重要?](#7.3 为什么内存很重要?)
[7.4 寄存器、内存和硬盘的区别](#7.4 寄存器、内存和硬盘的区别)
[7.5 为什么数组下标访问通常是 O(1)?](#7.5 为什么数组下标访问通常是 O(1)?)
[八、控制单元 CU:CPU 如何知道自己应该做什么?](#八、控制单元 CU:CPU 如何知道自己应该做什么?)
[8.1 什么是控制单元?](#8.1 什么是控制单元?)
[8.2 CPU 由哪些部件组成?](#8.2 CPU 由哪些部件组成?)
[九、指令与 PC 寄存器:CPU 是如何执行程序的?](#九、指令与 PC 寄存器:CPU 是如何执行程序的?)
[9.1 什么是指令?](#9.1 什么是指令?)
[9.2 什么是 PC 寄存器?](#9.2 什么是 PC 寄存器?)
[9.3 CPU 的基本指令执行周期](#9.3 CPU 的基本指令执行周期)
[9.4 时钟频率是什么?](#9.4 时钟频率是什么?)
[十、从机器语言到 Java:我们写的代码是如何运行的?](#十、从机器语言到 Java:我们写的代码是如何运行的?)
[10.1 什么是程序?](#10.1 什么是程序?)
[10.2 机器语言](#10.2 机器语言)
[10.3 汇编语言](#10.3 汇编语言)
[10.4 高级语言](#10.4 高级语言)
[10.5 Java 程序的运行过程](#10.5 Java 程序的运行过程)
[10.6 一条 Java 语句为什么可能对应多条机器指令?](#10.6 一条 Java 语句为什么可能对应多条机器指令?)
[11.1 什么是操作系统?](#11.1 什么是操作系统?)
[11.2 操作系统的两个重要目标](#11.2 操作系统的两个重要目标)
[11.3 什么是进程?](#11.3 什么是进程?)
[11.4 进程控制块 PCB](#11.4 进程控制块 PCB)
[11.5 CPU 调度:多个程序如何共享 CPU?](#11.5 CPU 调度:多个程序如何共享 CPU?)
[11.6 内存管理:不同程序如何避免互相干扰?](#11.6 内存管理:不同程序如何避免互相干扰?)
[11.7 进程间通信 IPC](#11.7 进程间通信 IPC)
[十二、把所有知识串起来:一个 Java 程序究竟经历了什么?](#十二、把所有知识串起来:一个 Java 程序究竟经历了什么?)
[第三步:JVM 加载并执行字节码](#第三步:JVM 加载并执行字节码)
[第四步:CPU 执行机器指令](#第四步:CPU 执行机器指令)
[第二阶段:理解 CPU 的工作原理](#第二阶段:理解 CPU 的工作原理)
内容大纲:
本文从计算机基础原理出发,系统梳理了从二进制、逻辑门、CPU架构到操作系统与Java程序运行的完整知识链条。通过冯·诺依曼体系结构,揭示计算机如何以0和1为基础,借助逻辑门构建算术逻辑单元(ALU),利用寄存器与内存协同工作,并由控制单元协调指令执行。文章深入解析了程序从编写、编译为字节码,经JVM解释或即时编译,最终由CPU执行机器指令的全过程,阐明了操作系统在进程管理、内存隔离与资源调度中的核心作用。旨在帮助初学者建立"硬件---系统---软件"一体化认知框架,理解编程背后的底层机制,为深入学习Java后端开发打下坚实基础。
一、前言:我们每天都在使用计算机,但真的了解它吗?
我们每天都在使用计算机。
打开浏览器、运行 Java 程序、编辑文档、玩游戏......这些操作看起来非常简单,但你有没有思考过下面几个问题?
-
计算机为什么只能识别 0 和 1?
-
CPU 到底是如何执行程序的?
-
内存和硬盘有什么区别?为什么程序运行时需要占用内存?
-
我们编写的 Java 代码,计算机真的能够直接看懂吗?
-
操作系统为什么可以同时运行多个程序?
如果你刚开始学习计算机,或者正在学习 Java 后端开发,那么理解这些问题非常有必要。
很多初学者学习编程时,只关注语法:变量怎么定义、循环怎么写、方法怎么调用。但如果我们进一步了解计算机的底层工作原理,就会发现,软件并不是凭空运行的。每一行代码最终都需要依赖计算机的硬件、指令系统以及操作系统才能完成相应的工作。
本文将从最基础的电子开关开始,逐步认识计算机的核心组成,理解 CPU 如何执行指令,以及 Java 程序如何在计算机中运行。
本文的学习路线:
计算机发展史 → 冯·诺依曼体系结构 → 逻辑门 → ALU → 寄存器与内存 → CPU 指令执行 → 编程语言 → 操作系统。
建议初学者按照顺序阅读,不需要一开始就记住所有细节。我们的目标是建立完整的知识框架,理解各个部件之间的关系。
二、计算机的发展:从简单的计算工具到电子计算机
2.1 为什么人类需要计算机?
计算的需求在人类历史上一直存在。
从早期的算盘等计算工具,到机械计算设备,再到现代电子计算机,人类一直在尝试让计算变得更快、更准确、更高效。
计算机的发展大致经历了以下阶段:
-
早期计算工具: 通过人工操作辅助计算。
-
机械计算设备: 利用机械结构完成特定计算。
-
电子计算机: 利用电子元件实现逻辑判断、数据存储和运算。
-
现代计算机: 通过高度集成的芯片,实现高速、稳定和复杂的计算。
现代计算机虽然外观、体积和性能差异很大,但其基本工作思想有许多共同之处。
它们都需要处理数据、执行操作、保存信息,并与外部设备进行交互。
2.2 计算机究竟是什么?
我们可以先用一句简单的话理解计算机:
计算机是一种能够按照指令处理数据,并将处理结果输出的电子设备。
例如,我们打开计算器,输入:
1 + 2
最后得到:
3
从用户的角度看,这只是一次简单的加法。
但从计算机内部来看,它需要接收输入、表示数据、执行计算,并向用户展示结果。
这就引出了一个重要问题:计算机内部由哪些部件组成?
三、冯·诺依曼体系结构:认识计算机的核心组成

现代通用计算机通常可以借助冯·诺依曼体系结构来理解。
这一体系结构的核心思想之一,是将程序指令和数据以适当的形式存储起来,由计算机按照指令执行相应操作。
3.1 计算机的五类基本组成
按照常见的冯·诺依曼体系结构介绍方式,我们可以认识以下组成部分:
| 组成部分 | 主要作用 | 通俗理解 |
|---|---|---|
| CPU | 执行指令、进行运算和控制 | 负责执行任务的核心 |
| 存储器 | 保存指令和数据 | 存放程序与数据的地方 |
| 输入设备 | 向计算机输入信息 | 键盘、鼠标等 |
| 输出设备 | 向用户展示处理结果 | 显示器、打印机等 |
| 控制相关部件 | 协调各部件执行工作 | 指挥执行过程 |
其中,CPU 和存储器是理解计算机运行原理的关键。
需要注意的是,这是一种帮助我们理解计算机的基本模型。现代计算机的实际硬件组织更加复杂,并不只是几个简单部件的直接组合。
3.2 计算机的工作过程
以打开一个计算器并进行加法运算为例,可以将整体过程抽象为:
用户输入数据
↓
计算机接收输入
↓
程序和数据进入相应的处理流程
↓
CPU 执行相关指令
↓
计算结果产生
↓
输出设备展示结果
这张流程图虽然简单,却包含了计算机工作的基本思想:
输入信息 → 处理信息 → 输出结果。
3.3 CPU、内存和硬盘有什么区别?
这是初学者经常遇到的问题。
| 部件 | 主要用途 | 特点 |
|---|---|---|
| CPU | 执行指令、处理数据 | 运算能力强,内部有高速存储部件 |
| 内存(RAM) | 暂存正在运行的程序和数据 | 通常速度较快,断电后数据会丢失 |
| 硬盘或 SSD | 长期保存文件和程序 | 容量通常较大,断电后数据仍可保留 |
可以把它们想象成一个工作场景:
-
硬盘: 存放资料的档案室。
-
内存: 工作时摆放资料的桌面。
-
CPU: 负责按照要求处理资料的人。
如果我们要运行一个 Java 程序,程序文件通常存放在硬盘或 SSD 中。程序启动后,操作系统会将执行所需的内容加载到内存,并由 CPU 执行相应指令。
这里还有一个值得注意的规律:
一般而言,存储容量越大,并不意味着访问速度越快。CPU 内部的寄存器容量很小但速度极快,内存容量更大,而硬盘或 SSD 通常提供更大的持久化存储空间。
这也是为什么计算机需要多层存储结构,而不是只使用一种存储设备。
四、从 0 和 1 开始:计算机为什么使用二进制?
4.1 什么是二进制?
我们日常生活中最熟悉的是十进制。
例如:
十进制:0、1、2、3、4、5、6、7、8、9
而二进制只使用两个数字:
二进制:0、1
十进制采用逢十进一,二进制采用逢二进一。
例如:
十进制 0 = 二进制 0
十进制 1 = 二进制 1
十进制 2 = 二进制 10
十进制 3 = 二进制 11
十进制 4 = 二进制 100
十进制 5 = 二进制 101
为什么计算机喜欢使用二进制呢?
一个重要原因是,电子电路可以通过不同的电气状态表示两种逻辑状态。
在抽象的数字电路中,我们通常将它们表示为:
-
0:逻辑假。 -
1:逻辑真。
实际电路使用电压范围等物理条件区分逻辑状态,并不是说计算机内部真的存在写着 0 和 1 的小格子。
4.2 什么是 bit 和 byte?
计算机使用二进制表示数据,因此我们需要认识两个基础单位。
bit(比特): 最基本的信息单位,可以表示 0 或 1。
byte(字节): 通常由 8 个 bit 组成。
例如:
1 byte = 8 bit
8 个二进制位可以表示 (2^8=256) 种不同的位组合。
因此,一个字节可以表示 0 到 255 之间的无符号整数。
需要注意,实际数据的含义还取决于编码方式和数据类型。同一组二进制位可以被解释为整数、字符的一部分,或者其他数据。
4.3 二进制如何表示十进制数字?
以二进制 1011 为例。
从右往左,每一位对应的权重分别为:
位权: 8 4 2 1
二进制:1 0 1 1
因此:
1011(二进制)
= 1×8 + 0×4 + 1×2 + 1×1
= 11(十进制)
掌握这个原理后,我们就能理解计算机如何通过二进制表示数字。
但是,计算机不仅需要表示数字,还需要完成加法、比较、判断等操作。
这些操作是如何实现的?
这就需要认识逻辑门。
五、逻辑门:计算机如何实现最基本的逻辑运算?
5.1 从电子开关说起
在早期的计算设备中,人们可以利用机械继电器等装置实现开关控制。
开关只有两种基本状态:打开和关闭。
如果把这两种状态抽象成数字逻辑,就可以表示为 0 和 1。
现代计算机主要依靠晶体管等半导体器件构建数字电路。虽然具体的物理实现与机械开关不同,但我们仍然可以通过开关和逻辑状态的类比来理解数字电路的基本思想。
当大量电子器件按照一定规则连接起来,就可以实现更复杂的逻辑运算。
5.2 什么是逻辑门?
逻辑门是数字电路中的基本逻辑单元。
常见的逻辑门包括:
-
非门(NOT)
-
与门(AND)
-
或门(OR)
-
异或门(XOR)
我们分别来看。
5.3 非门(NOT)
非门的作用是取反。
输入为 0,输出为 1;输入为 1,输出为 0。
| 输入 A | 输出 Y |
|---|---|
| 0 | 1 |
| 1 | 0 |
可以用下面的表达式表示:
Y = NOT A
在编程中,我们也能看到类似的逻辑思想。例如 Java 中的逻辑非运算符 !:
boolean flag = true;
System.out.println(!flag); // false
注意:Java 的 boolean 类型是编程语言提供的逻辑类型。这里是逻辑思想上的类比,不代表 Java 代码中的每一个操作都直接对应一个独立的非门。
5.4 与门(AND)
与门要求所有输入都为 1,输出才为 1。
| 输入 A | 输入 B | 输出 Y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
对应的逻辑关系是:
Y = A AND B
Java 中可以看到类似的逻辑运算:
boolean a = true;
boolean b = false;
System.out.println(a && b); // false
5.5 或门(OR)
只要有一个输入为 1,输出就为 1。
| 输入 A | 输入 B | 输出 Y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
Java 示例:
boolean a = true;
boolean b = false;
System.out.println(a || b); // true
5.6 异或门(XOR)
异或门在两个输入不同时输出 1,相同时输出 0。
| 输入 A | 输入 B | 输出 Y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
5.7 为什么逻辑门如此重要?
单独一个逻辑门能完成的事情非常有限。
但是,如果我们将大量逻辑门按照一定规则组合起来,就可以实现更复杂的功能,例如:
-
二进制加法器;
-
比较电路;
-
数据选择电路;
-
寄存器;
-
算术逻辑单元。
换句话说:
计算机复杂的计算能力,建立在大量简单数字电路相互配合的基础上。
这也是理解计算机底层原理的一条重要思路:复杂系统往往由更简单的部件逐层构建而成。
六、ALU:CPU 中负责运算的核心部件
6.1 什么是 ALU?
ALU 的全称是 Arithmetic Logic Unit,中文通常称为算术逻辑单元。
它是处理器中的重要组成部分,主要负责执行算术运算和逻辑运算。
例如:
1 + 2
5 - 3
比较 A 和 B
执行逻辑运算
这些操作都可以通过处理器中的相应运算电路来完成。
需要注意,现代处理器中还包含浮点运算单元、向量运算单元等其他执行部件,实际结构远比一个简单的 ALU 更复杂。
6.2 ALU 是如何实现加法的?
我们先看一个简单的二进制加法:
0
+ 1
---
1
再看:
1
+ 1
---
10
这里的 10 是二进制,表示十进制的 2。
为什么两个 1 相加会产生进位?
因为二进制采用逢二进一。
当我们将多个二进制位的加法电路组合起来,就可以构建更宽的加法器。例如,一个 8 位加法器可以处理两个 8 位二进制数的加法,并根据具体设计产生进位信息。
进一步组合相关逻辑电路,还可以实现减法、比较等操作。
6.3 ALU 和 Java 中的加法有什么关系?
例如,我们编写:
int a = 10;
int b = 20;
int sum = a + b;
System.out.println(sum);
我们看到的只是一个加法运算。
但程序运行时,Java 代码需要经过相应的执行过程,最终由处理器执行对应的机器指令。对于普通整数加法,这些指令可能使用处理器的整数运算单元完成计算。
因此,我们可以建立这样的认识:
Java 中的加法表达式
↓
编译或运行时处理
↓
对应的机器指令
↓
CPU 执行运算
↓
产生计算结果
这只是为了理解而进行的简化流程。实际执行过程会受到编译器优化、JVM 实现、处理器架构等因素影响。
我们不需要直接操作逻辑门,依然可以使用 Java 完成复杂计算,因为底层硬件已经为我们提供了相应的运算能力。
七、寄存器与内存:计算机如何保存数据?
如果 CPU 只能计算,却无法保存需要处理的数据,那么计算机就很难完成复杂任务。
因此,存储也是计算机的重要组成部分。
7.1 什么是寄存器?
寄存器是处理器内部容量很小、访问速度很快的存储部件。
它可以保存当前计算所需要的数据、地址或控制信息。
例如,在执行某个运算时,处理器可能需要暂存操作数、中间结果以及后续执行所需的信息。
寄存器的数量和用途由具体的处理器架构决定,不同寄存器承担的职责也可能不同。
7.2 什么是 RAM?
RAM 的全称是 Random Access Memory,中文通常称为随机存取存储器。
它用于保存程序运行过程中需要访问的数据和指令。
RAM 的一个重要特点是可以按照地址访问存储位置,而不需要像某些顺序存储设备那样从头开始逐项读取。
在日常使用中,我们常说的计算机内存通常主要指 RAM。
一般而言,RAM 属于易失性存储器:断电后,其中的数据通常不能继续保留。
7.3 为什么内存很重要?
假设我们正在运行一个 Java 程序。
程序文件可能存放在 SSD 中,但程序启动后,操作系统和 JVM 需要为程序运行准备相应的内存资源。
程序运行时会涉及各种数据,例如:
-
对象和数组;
-
局部变量;
-
运行时需要的数据结构;
-
类的相关信息;
-
程序执行过程中产生的中间数据。
这些内容在具体实现中可能位于不同的内存区域,不能简单地认为所有 Java 数据都存放在同一个地方。
但从整体上看,内存为程序运行时的数据访问和处理提供了重要的存储空间。
7.4 寄存器、内存和硬盘的区别
| 对比项 | 寄存器 | RAM 内存 | SSD / 硬盘 |
|---|---|---|---|
| 主要位置 | CPU 内部 | 主存储器 | 外部持久化存储设备 |
| 容量 | 很小 | 通常较大 | 通常更大 |
| 访问速度 | 通常最快 | 通常较快 | 通常低于内存 |
| 断电保留数据 | 通常不能 | 通常不能 | 通常可以 |
| 主要用途 | 暂存处理器需要的信息 | 保存运行中的数据 | 长期保存程序和文件 |
可以把它们理解成不同层次的存储系统:每一层都有自己的设计目的,而不是简单地相互替代。
7.5 为什么数组下标访问通常是 O(1)?
我们在 Java 中经常这样写:
int[] numbers = {10, 20, 30, 40, 50};
System.out.println(numbers[2]);
输出结果为:
30
为什么通过下标访问数组通常具有 O(1) 的时间复杂度?
对于普通数组,运行时可以根据数组的起始位置、元素大小和下标计算目标元素的位置。
可以用下面的方式理解:
目标位置 = 起始位置 + 下标 × 每个元素占用的空间
因此,在通常的随机访问模型下,定位数组中的某个元素不需要从第一个元素开始逐个查找。
不过,O(1) 描述的是渐近时间复杂度,不代表每次访问的实际耗时完全相同,也不代表所有数据结构都能通过下标实现 O(1) 访问。
例如,链表按下标寻找元素通常需要逐个遍历节点。
八、控制单元 CU:CPU 如何知道自己应该做什么?
我们已经认识了运算部件和存储部件。
但是,只有计算能力和存储能力还不够。计算机还需要知道:
-
当前应该执行什么操作?
-
应该使用哪些数据?
-
下一步应该做什么?
这就需要控制相关的硬件机制。
8.1 什么是控制单元?
CU 的全称是 Control Unit,中文称为控制单元。
在基础模型中,我们可以把它理解为负责协调指令执行的控制部件。
它会根据指令的内容和处理器当前的状态,协调数据传送、运算以及其他相关操作。
需要注意,现代 CPU 的控制逻辑可能采用复杂的流水线、乱序执行和其他机制,并不完全等同于一个简单的顺序控制器。
8.2 CPU 由哪些部件组成?
为了方便入门,我们可以使用下面这个简化模型:
CPU
┌───────────────────┐
│ │
│ 控制单元 CU │
│ ↓ │
│ 运算单元 ALU │
│ ↕ │
│ 寄存器 │
│ │
└───────────────────┘
↕
内存 RAM
在这个模型中:
-
ALU 负责执行相应的算术和逻辑运算;
-
寄存器用于暂存数据、地址和状态等信息;
-
控制单元协调指令的执行;
-
内存保存程序指令和运行所需的数据。
它们并不是彼此独立工作的,而是通过处理器内部及处理器与内存之间的数据通路协同工作。
九、指令与 PC 寄存器:CPU 是如何执行程序的?
这一部分非常重要,因为它将前面介绍的硬件知识串联了起来。
9.1 什么是指令?
指令是处理器可以执行的基本操作的编码表示。
从入门角度来看,一条指令通常包含:
-
操作码: 表示要执行什么操作。
-
操作数或相关信息: 指定操作所需的数据、寄存器、地址等。
具体的指令格式由处理器的指令集架构决定,不同架构之间可能存在明显差异。
例如,我们可以用伪指令表示:
LOAD A
LOAD B
ADD
STORE C
这里使用的是帮助理解的示意形式,并不代表某一种真实 CPU 的完整指令集。
可以将其理解为:
-
读取数据 A;
-
读取数据 B;
-
执行加法;
-
保存计算结果 C。
一组按照一定顺序组织起来的指令,就能够完成更复杂的任务。
9.2 什么是 PC 寄存器?
PC 通常是 Program Counter 的缩写,也常被称为程序计数器。
它是处理器中与指令执行位置有关的重要寄存器。
在基础模型中,PC 保存或指示下一条需要获取的指令的位置。
例如,假设一段示意程序由以下几条指令组成:
地址 100:读取数据
地址 104:执行加法
地址 108:保存结果
地址 112:结束程序
这里的地址仅用于说明,并不是所有处理器的指令地址都按 4 递增。
在一个简单的顺序执行模型中,处理器完成当前指令后,会按照相应规则确定后续指令的位置。
如果遇到跳转指令,后续执行位置就可能改变。
因此,PC 对指令执行流程具有重要作用。
9.3 CPU 的基本指令执行周期
在入门教材中,我们通常将指令执行过程概括为几个阶段:
-
取指: 获取需要执行的指令。
-
译码: 解析指令的操作和相关信息。
-
执行: 执行对应的运算或操作。
-
写回: 在需要时保存运算结果。
可以用流程图表示:
┌──────────┐
│ 取指 │
└────┬─────┘
↓
┌──────────┐
│ 译码 │
└────┬─────┘
↓
┌──────────┐
│ 执行 │
└────┬─────┘
↓
┌──────────┐
│ 写回 │
└────┬─────┘
↓
后续指令
这是为了理解而采用的简化模型。真实 CPU 可能同时处理多条处于不同执行阶段的指令,也可能采用更复杂的执行流程。
9.4 时钟频率是什么?
CPU 的工作受到时钟等时序机制的控制。
时钟频率表示时钟信号每秒变化周期的次数,常用 Hz、MHz 或 GHz 表示。
例如,某个时钟信号的频率为 3 GHz,意味着每秒有约 30 亿个时钟周期。
但是,这不意味着 CPU 每秒只能执行 30 亿条指令,也不意味着频率越高,所有程序就一定越快。
不同指令可能需要不同的执行资源和周期数,现代处理器还可能采用多核、流水线、乱序执行等技术。
因此,CPU 性能还受到架构、缓存、内存访问、并行能力和具体工作负载等多种因素影响。
十、从机器语言到 Java:我们写的代码是如何运行的?
到目前为止,我们已经认识了计算机的基本硬件,也了解了 CPU 执行指令的基本过程。
接下来,我们需要解决另一个问题:
既然 CPU 执行的是机器指令,那么我们编写的 Java 代码是如何变成计算机可以执行的操作的?
10.1 什么是程序?
从计算机科学的角度来看,程序可以理解为指令以及这些指令所处理的数据的组合。
我们平时看到的程序,通常表现为一组文件。
例如,一个简单的 Java 程序:
java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
我们编写的是人类比较容易理解的 Java 代码,而不是直接用 0 和 1 编写所有机器指令。
这是因为计算机技术的发展不断提高了编程语言的抽象层次。
10.2 机器语言
机器语言使用处理器能够识别的指令编码表示操作。
从历史上看,早期程序员需要直接操作硬件开关,或者使用非常接近机器指令的方式编写程序。
这种方式能够直接表达底层操作,但开发难度很高,也不利于大型程序的维护。
现代程序员通常不需要手动输入一长串二进制机器码来开发普通应用程序。
10.3 汇编语言
汇编语言使用助记符表示机器指令。
例如,某种处理器的汇编语言可能使用类似下面的指令:
MOV
ADD
SUB
具体含义取决于处理器架构和指令语法。
与直接阅读二进制编码相比,助记符更容易理解。
汇编语言通常需要通过汇编器转换为相应的机器代码。
不过,不同处理器架构的指令集可能不同,因此汇编程序通常具有较强的硬件平台相关性。
10.4 高级语言
高级语言让程序员可以使用更接近人类思维和业务逻辑的方式编写程序。
例如,Java 中的加法:
java
int a = 10;
int b = 20;
int sum = a + b;
我们只需要表达业务逻辑,而不必手动设计加法器,也不必逐条指定 CPU 内部的所有操作。
编译器、运行时和底层硬件共同完成后续工作。
这就是高级语言的价值:它隐藏了大量硬件细节,让程序员能够更专注于问题本身。
10.5 Java 程序的运行过程
Java 与 C 等传统编译型语言存在一些不同。
一个常见的 Java 程序运行过程如下:
Java 源代码
↓
Java 编译器 javac
↓
字节码文件 .class
↓
JVM 加载并执行字节码
↓
解释执行或 JIT 编译等机制
↓
处理器执行相应的机器指令
这里需要理解几个关键概念。
第一,Java 源代码。
就是我们编写的 .java 文件。
第二,Java 编译器。
通常使用 javac 将 Java 源代码编译成 Java 字节码,生成 .class 文件。
第三,JVM。
JVM 是 Java Virtual Machine,即 Java 虚拟机。它为 Java 字节码提供运行环境,并负责执行相关指令。
第四,JIT 编译。
JIT 是 Just-In-Time 的缩写,即即时编译。部分 JVM 实现可以将经常执行的字节码编译为本机机器代码,以改善执行性能。
因此,Java 并不是简单地将每一条源代码直接翻译成一条机器指令。Java 程序的实际执行过程可能涉及字节码解释、即时编译以及多种运行时优化。
10.6 一条 Java 语句为什么可能对应多条机器指令?
例如:
java
int sum = a + b;
从源代码来看,它只是一条简单语句。
但实际运行时,可能涉及读取变量值、执行整数加法、保存结果等操作。
最终生成多少条机器指令,要取决于编译器、JVM 优化以及处理器架构。
因此,我们不能认为 Java 的一条语句必然对应一条机器指令。
这个认识也能帮助我们理解为什么程序的执行效率不仅取决于代码写了多少行,还取决于算法、编译器优化、运行时环境以及底层硬件。
十一、操作系统:计算机中的"大管家"
现在,我们已经理解了 CPU 如何执行指令,也知道程序运行需要使用内存。
但还有一个问题:
如果计算机上同时运行浏览器、音乐播放器、IDE 和 Java 程序,那么谁负责分配 CPU 时间、管理内存,并协调这些程序的运行?
答案就是操作系统。
11.1 什么是操作系统?
操作系统是负责管理计算机资源,并向应用程序提供运行环境和服务的一类系统软件。
常见的操作系统包括:
-
Windows;
-
Linux;
-
macOS;
-
Android;
-
iOS;
-
鸿蒙等。
不同操作系统的设计和实现各不相同,但它们都需要处理资源管理、程序执行以及与硬件交互等问题。
11.2 操作系统的两个重要目标
可以从两个方面理解操作系统的作用。
第一,管理和保护计算机资源。
操作系统需要协调 CPU、内存和各种设备的使用,减少不同程序之间的冲突,并提供必要的隔离与保护机制。
第二,为应用程序提供统一的使用方式。
应用程序不必为每一种硬件设备都重新编写一整套底层控制代码,而是可以通过操作系统提供的接口完成文件操作、网络通信、内存申请等工作。
这让软件开发变得更加方便。
11.3 什么是进程?
进程是操作系统中的一个重要概念。
简单来说,进程可以理解为程序的一次运行实例,也是操作系统进行资源管理的重要单位。
例如:
你打开两个浏览器窗口,或者启动两个不同的 Java 程序,系统可能会创建多个进程来承载它们的运行。
需要注意,应用程序与进程并不是完全相同的概念。
程序通常指代码及相关文件,而进程涉及程序运行时的状态、资源和执行环境。
同一个程序可以在不同时间启动多次,从而形成多个运行实例。
11.4 进程控制块 PCB
操作系统需要管理大量运行中的进程,因此需要保存每个进程的相关信息。
进程控制块的英文是 Process Control Block,简称 PCB。
PCB 可以帮助操作系统记录和管理进程的相关状态。
从概念上看,它可能涉及:
-
进程标识信息;
-
进程运行状态;
-
调度相关信息;
-
内存管理相关信息;
-
其他管理进程所需的信息。
可以用一个简化的 Java 类来帮助理解:
java
class ProcessInfo {
int pid; // 进程标识
String state; // 进程状态
int priority; // 示例:调度相关信息
}
这只是帮助理解的伪模型,并不是实际操作系统的 PCB 实现。
真实 PCB 由操作系统内核管理,其数据结构和字段会随操作系统而变化。
11.5 CPU 调度:多个程序如何共享 CPU?
假设你的电脑正在运行:
-
浏览器;
-
音乐播放器;
-
Java 开发工具;
-
后台下载程序。
这些程序都可能需要 CPU 资源。
在单核处理器的简化模型中,操作系统可以让不同的进程在不同的时间段使用 CPU。
其基本思想是:
进程 A 使用 CPU
↓
调度切换
↓
进程 B 使用 CPU
↓
调度切换
↓
进程 C 使用 CPU
这种机制让多个程序都能够获得执行机会,从而形成多个任务同时推进的效果。
现代操作系统还会考虑优先级、响应时间、处理器亲和性等因素,而现代 CPU 也可能具有多个核心。因此,真实调度过程比这个示意图更加复杂。
11.6 内存管理:不同程序如何避免互相干扰?
如果多个程序都可以随意访问和修改所有内存,那么一个程序的错误就可能影响其他程序。
因此,现代操作系统通常通过虚拟内存、地址空间隔离和权限控制等机制,管理进程对内存的访问。
例如:
进程 A
└── 自己的虚拟地址空间
进程 B
└── 自己的虚拟地址空间
进程 C
└── 自己的虚拟地址空间
这里的"自己的地址空间"不意味着每个进程一定拥有独立的全部物理内存,而是说操作系统为进程提供了相应的地址空间抽象和访问保护。
这也解释了为什么一个程序通常不能随意读取另一个程序的私有内存。
11.7 进程间通信 IPC
既然进程之间通常具有隔离性,那么它们如果需要协同工作,该怎么办?
这就需要进程间通信。
IPC 的全称是 Inter-Process Communication,即进程间通信。
常见机制包括:
-
管道;
-
共享内存;
-
文件;
-
网络通信;
-
信号量;
-
信号等。
这些机制适用于不同的通信场景。
例如,一个程序可以通过管道将数据传递给另一个程序;多个进程也可以通过共享内存交换数据。
网络通信则不仅可以用于同一台主机上的不同进程,还可以用于不同主机上的进程之间交换信息。
理解 IPC 后,我们就能进一步认识到:复杂应用往往不是孤立的单个程序,而是多个组件通过一定的通信机制协同完成任务。
十二、把所有知识串起来:一个 Java 程序究竟经历了什么?
现在,让我们回到文章开头的问题。
假设我们编写了下面这个 Java 程序:
java
public class SumDemo {
public static void main(String[] args) {
int a = 10;
int b = 20;
int sum = a + b;
System.out.println(sum);
}
}
运行之后,控制台输出:
30
那么,从底层原理的角度看,这个程序经历了什么?
第一步:源代码被编译
我们编写的 .java 文件会通过 Java 编译器转换为 .class 字节码文件。
第二步:操作系统启动程序
操作系统负责创建或管理相应的进程,并为程序提供运行所需的资源。
在常见的 Java 运行方式下,JVM 本身运行在操作系统提供的环境中。
第三步:JVM 加载并执行字节码
JVM 加载程序所需的类,并通过解释执行、即时编译等机制执行程序。
第四步:CPU 执行机器指令
经过相关执行过程后,处理器会执行相应的机器指令,完成整数运算以及其他操作。
第五步:输出结果
程序通过 Java 提供的输出接口打印结果。底层运行时和操作系统再通过相应的输出机制,将结果展示在终端或控制台中。
整体流程可以概括为:
编写 Java 源代码
↓
javac 编译
↓
生成 .class 字节码
↓
启动 JVM 运行环境
↓
操作系统提供进程和资源
↓
JVM 执行字节码
↓
CPU 执行相应机器指令
↓
程序产生结果
↓
控制台显示 30
需要注意,这是一张概念流程图,而不是所有 Java 程序都完全相同的逐步执行轨迹。例如,JIT 编译可能在程序运行过程中发生,输出也可能经过不同的系统组件。
但它足以帮助我们建立一个整体认识:
Java 程序、JVM、操作系统和 CPU 并不是彼此孤立的,它们处于不同的软件与硬件层次,共同完成程序的运行。
十三、初学者应该如何学习计算机底层原理?
如果你目前正在学习 Java,不需要立刻深入研究每一个硬件细节。更重要的是建立知识体系,再结合编程实践逐步深入。
建议按照以下顺序学习。
第一阶段:理解计算机的基本组成
重点掌握:
-
CPU、内存和硬盘的区别;
-
二进制的基本表示方法;
-
bit 与 byte;
-
输入、处理和输出的基本流程。
学习目标:能够说清楚程序运行需要哪些基本资源。
第二阶段:理解 CPU 的工作原理
重点掌握:
-
逻辑门;
-
ALU;
-
寄存器;
-
指令;
-
PC 寄存器;
-
取指、译码、执行等基本阶段。
学习目标:理解 CPU 如何按照指令完成操作。
第三阶段:理解编程语言与运行时
重点掌握:
-
机器语言与汇编语言;
-
编译器的作用;
-
Java 字节码;
-
JVM 的基本职责;
-
解释执行与 JIT 编译。
学习目标:能够解释 Java 源代码为什么不能直接等同于 CPU 机器指令。
第四阶段:理解操作系统
重点掌握:
-
进程;
-
线程;
-
CPU 调度;
-
内存管理;
-
进程间通信。
学习目标:理解多个程序如何共享计算机资源,以及操作系统为什么重要。
完成这些基础内容后,可以进一步学习 JVM 内存结构、Java 垃圾回收、线程调度、计算机网络等知识。