CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
计算机科学入门/课时/计算机如何工作
RawBlame
课时目录12 节

计算机科学入门

  1. 01计算机如何工作
  2. 02数据结构
  3. 03算法与搜索排序
  4. 04复杂度分析
  5. 05离散数学基础
  6. 06操作系统概念
  7. 07数据库原理
  8. 08计算机网络
  9. 09系统设计入门
  10. 10安全基础
  11. 11设计模式与 UML
  12. 12综合练习

计算机如何工作

CPU、内存、pick-a-language 与编程语言选型。

30 分钟

计算机如何工作

这一课你将能做到

  1. 画出冯·诺依曼体系结构,说清楚 CPU、内存、总线在程序运行时的分工
  2. 描述一条机器指令从「取指」到「执行」的完整过程,并解释 CPU 主频、缓存为什么影响性能
  3. 说清楚 Java 代码从 .java 到运行结果经历的编译、类加载、解释执行与 JIT 过程

为什么要学

很多初学者写了一年 Spring Boot,依然回答不出这三个线上问题:为什么加了机器 CPU 核数接口却没更快?为什么循环里访问大数组有时候快、有时候慢几百倍?为什么 JVM 预热之后接口延迟会下降?

这些问题的答案都不在框架里,而在硬件和执行模型里。本课程后面的数据结构、算法、操作系统、网络知识,全部建立在「程序最终是 CPU 一条条执行指令、内存一格格存取数据」这个事实上。本课先把这张全景图搭起来,之后每节课都是往图里填细节。

结合本站的 Spring Boot 判题平台理解:用户在线提交一段 Java 代码,平台编译、运行、比对输出——这条链路恰好把「编译执行」的每个环节都走了一遍,是最具体的学习样本。

先建立图像

冯·诺依曼结构的核心思想只有一句:程序和数据以同样的二进制形式存放在同一个存储器里。

        ┌──────────────────────────────────────────────┐
        │                    内存 Memory                │
        │  ┌────────────┬────────────┬───────────────┐ │
        │  │ 机器码指令  │ 机器码指令  │ 数据/堆/栈... │ │
        │  │ 010110...  │ 111000...  │  ...          │ │
        │  └────────────┴────────────┴───────────────┘ │
        └───────▲───────────────────┬───────────────────┘
                │ 取指令/读数据       │ 写结果
        ┌───────┴───────────────────▼───────────────────┐
        │                     CPU                       │
        │  ┌─────────┐  ┌──────────┐  ┌──────────────┐ │
        │  │  PC 程序 │  │ 寄存器    │  │ ALU 算术逻辑  │ │
        │  │ 计数器   │→ │ Registers │→ │ 单元(加减与)  │ │
        │  └─────────┘  └──────────┘  └──────────────┘ │
        │       │ 控制单元 CU 按节拍驱动上述部件          │
        └───────┼───────────────────────────────────────┘
                │ 系统总线(地址总线 / 数据总线 / 控制总线)
        ┌───────▼────────┐  ┌──────────────┐
        │  SSD/磁盘       │  │ 网卡/键盘/... │
        └────────────────┘  └──────────────┘

关键直觉:CPU 极快(纳秒级),内存慢约百倍,磁盘慢约十万倍。所以「让数据待在离计算近的地方」是所有性能优化的总根源——CPU 缓存是这样,Redis 是这样,判题平台把热题放进本地缓存也是这样。

跟着做

步骤 1:观察一条指令的一生

CPU 执行任何程序,底层都是无限循环下面四步(指令周期):

  1. 取指 Fetch:按 PC(程序计数器)里的地址,从内存把指令读到 CPU
  2. 译码 Decode:控制单元搞清楚这是加法还是跳转,操作数在哪
  3. 执行 Execute:ALU 做运算,或访问内存
  4. 写回 Write-back:结果放进寄存器,PC 指向下一条指令

写一段 Java 代码,用命令看到它真实的字节码:

public class InstructionDemo {
    public static int add(int a, int b) {
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        System.out.println(add(3, 5));
    }
}

保存为 InstructionDemo.java,在终端执行:

javac InstructionDemo.java
javap -c InstructionDemo

javap -c 会反汇编出字节码,你会看到 iload_0(把局部变量 0 压入操作数栈)、iadd(整数相加)、ireturn(返回)。字节码不是机器码,它是 JVM 这套「虚拟计算机」的指令集——这正是 Java「一次编写,到处运行」的机制:同一份 .class 可以在 Windows、Linux、Mac 的不同真实 CPU 上跑,因为每个平台都有各自的 JVM 负责把字节码翻译成本机指令。

步骤 2:理解编译型、解释型与 Java 的混合路线

C/C++:  源码 ──编译器(提前编译 AOT)──▶ 特定平台机器码 ──▶ 直接执行,启动快峰值高
Python: 源码 ───────────────────────▶ 解释器逐行读取执行,灵活但慢
Java:   源码 ──javac──▶ 字节码 ──▶ JVM 解释执行
                                  └─热点代码─▶ JIT 即时编译为本机机器码(带缓存)

JIT 是 Java 性能的关键:方法被调用足够多次(热点),JVM 把它编译成优化过的本机码并缓存,越跑越快。这也解释了为什么做压测时要先「预热」,判题平台给每个判题任务设置超时上限时,要考虑 JVM 启动本身的固定开销——生产中判题服务是常驻进程,避免每份提交都重启 JVM,而是用预热好的工作进程池。

步骤 3:体会 CPU 缓存与局部性

public class LocalityDemo {
    public static void main(String[] args) {
        final int N = 10_000;
        int[][] a = new int[N][N];

        long t1 = System.nanoTime();
        long sum1 = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                sum1 += a[i][j];          // 按行访问:与内存布局一致,缓存友好
            }
        }
        long t2 = System.nanoTime();

        long sum2 = 0;
        for (int j = 0; j < N; j++) {
            for (int i = 0; i < N; i++) {
                sum2 += a[i][j];          // 按列访问:每次跳到下一行,缓存频繁失效
            }
        }
        long t3 = System.nanoTime();

        System.out.println("按行耗时(ms): " + (t2 - t1) / 1_000_000 + ", 和=" + sum1);
        System.out.println("按列耗时(ms): " + (t3 - t2) / 1_000_000 + ", 和=" + sum2);
    }
}

两段代码逻辑完全等价,按列版本通常慢几倍到十几倍。原因是 Java 多维数组在内存里「一行接一行」排列,CPU 从内存搬数据是按缓存行(通常 64 字节,即 16 个 int)成批搬的。顺序访问时,搬一次用 16 次;跳跃访问时,搬一次只用 1 次。这就是空间局部性;而循环里反复用到的变量会一直留在寄存器/缓存里,叫时间局部性。

步骤 4:串起判题平台的执行链路

用户在浏览器点「提交」,后端发生的事可以这样对应到本课知识:

  1. Spring Boot 接口收到源码字符串,写入工作目录的 .java 文件(内存 → 磁盘)
  2. 调用 javac(或使用 Java Compiler API)把源码编译成 .class
  3. 用隔离的 JVM 进程(或容器)加载字节码,解释/JIT 执行,喂入测试输入
  4. 收集标准输出与标准答案比对,结果写回 MySQL,热门题目的统计信息缓存在 Redis

整条链路就是「编译 → 加载 → 执行 → 存储」的具象版。

讲清楚要点

  1. 冯·诺依曼结构 = 运算器 + 控制器 + 存储器 + 输入 + 输出,现代 CPU 把运算器和控制器合在一块芯片里;「存储程序」(程序也放在内存里、可以被当作数据修改)是它区别于早期插线板计算机的根本。
  2. 寄存器 → L1/L2/L3 缓存 → 内存 → SSD → 网络/磁盘,容量递增、速度递减、单价递减。算法的常数项差异、各种缓存设计,本质都在利用这条层次结构。
  3. CPU 只认识机器码,机器码由操作码 + 操作数组成;指令是「加载、存储、运算、跳转」这几类的组合,所有复杂程序最终都塌缩成它们。
  4. Java 经历两级翻译:javac 做与平台无关的源码到字节码翻译(只做少量优化);JVM 在运行时再做解释或 JIT,重型优化(内联、逃逸分析、锁消除)主要发生在 JIT 阶段。
  5. 位是物理的,字节是逻辑的:内存按字节编址,每个地址唯一指向一个字节;多字节数据有大小端问题,Java 在虚拟机层面屏蔽了大小端,但跨语言通信(如自定义网络协议)时会遇到。

你可能会懵的地方

  • 「32 位 / 64 位」到底指什么? 主要指 CPU 通用寄存器一次能处理的数据宽度,以及内存地址的宽度。64 位机器的地址空间理论上可达 2⁶⁴ 字节,所以能使用远超 4GB 的内存;它并不意味着「一次算 64 位所以快一倍」。

  • 主频 3GHz 代表每秒执行 30 亿条指令吗? 约等于「每秒 30 亿个时钟周期」,不等于指令数。现代 CPU 一个周期可以执行多条指令(流水线、超标量),但访问内存卡顿一次可能浪费上百个周期,所以实际 IPC(每周期指令数)波动很大。

  • 字节码和机器码是什么关系?JVM 是用什么写的? 字节码是 JVM 规范定义的中间指令;JVM(如 HotSpot)本身是 C++ 写的原生程序,负责把字节码解释或编译成所在 CPU 的机器码。

  • 既然 JIT 会优化,写代码还需要关心性能吗? 要。JIT 擅长优化「小而热」的方法和可预测的模式,但它救不了 O(n²) 的算法、缺索引的全表扫描、把大数据集在堆里反复拷贝这类结构性浪费。

  • 进程崩溃了内存里的数据会丢吗? 会。进程退出后操作系统回收它的内存页,所以提交记录、判题结果必须落到 MySQL 或磁盘——这正是「持久化」存在的理由,下一课的存储层次和之后的数据库课都会用到。

错法对照

错法问题正解
把 CPU 占用高简单等同于「算得太多」自旋锁、频繁 GC、缺页中断也会占满 CPU用线程栈、GC 日志区分「有效计算」与空转
压测一上来就测延迟并下结论JIT 未预热、缓存未填充,数据失真先预热若干轮再统计稳态指标
循环内按列遍历大二维数组缓存行利用率低,慢数倍按内存布局顺序访问;必要时改数据结构
认为 JVM 内存等于物理内存JVM 有堆/栈/元空间划分,受 -Xmx 等参数限制理解内存层次与 JVM 内存区域,再谈调优
判题直接在 Web 服务进程里执行用户代码一次 System.exit 或死循环拖垮整个平台隔离进程/容器执行,限制内存、CPU、时间

卡点急救

  1. 接口延迟忽高忽低:先区分是 CPU 密集(看 CPU 使用率与热点方法)、I/O 等待(看磁盘/网络/锁)还是 GC 停顿(看 GC 日志),三者的解法完全不同。
  2. 怀疑某段代码「慢」:不要凭感觉,用 JMH 做微基准并先预热;手写 System.nanoTime() 计时时至少跑多次取均值,避免把 JIT 编译期算进去。
  3. 判题任务超时或内存溢出:先确认限制是否真正下发到执行进程(-Xmx、容器 cgroup 限制),再排查用户代码;平台自身的 JVM 与被执行代码的 JVM 必须隔离。

面试常问

  • 请描述从源码到运行,Java 经历了哪些阶段? 源码经 javac 编译为字节码;类加载器加载、验证、准备、解析;执行引擎先解释执行,热点方法由 JIT 编译为本机码并缓存。可以补充 AOT(GraalVM Native Image)是新趋势,用构建期编译换启动速度。

  • CPU 缓存为什么能提升性能?什么代码对缓存不友好? 利用程序的时间与空间局部性,以缓存行为单位批量取数据。对缓存不友好的典型是:随机访问大数组、按列遍历行优先存储的矩阵、链表式的指针追逐(每个节点散落在内存各处)。

  • 冯·诺依曼结构的核心是什么?瓶颈在哪? 核心是存储程序、指令与数据同存于一个存储器。瓶颈是 CPU 与内存之间的速度差(冯·诺依曼瓶颈),缓存、预取、向量化、让数据靠近计算(GPU/边缘缓存)都是在缓解它。

自测三问

  1. 不看资料,画出 CPU、内存、磁盘与总线,并标出指令周期四步发生在哪里。
  2. 解释同一份 .class 文件为什么能在不同操作系统上运行,代价是什么。
  3. 两段逻辑等价的二重循环,凭什么判断哪个更快?如何用实验验证?

小结一句话

计算机就是「CPU 按地址从内存取指令和数据、算完写回去」的无限循环,而 Java 在这之上加了字节码与 JIT 两层翻译——理解这条链路,性能问题就有了统一的思考起点。

延伸

  • 《深入理解计算机系统》(CSAPP)主页——建立「代码 → 机器 → 操作系统」全景图景的权威教材,重点读第 1、3、6 章
  • Java 虚拟机规范(SE 版)——字节码与类加载的官方权威定义
  • HotSpot 性能优化:JIT 编译概述(Oracle 官方文档)
  • 下一课:数据结构——数组、链表、栈、队列、哈希表与树,看看这些「摆放数据的方式」如何被 CPU 缓存和算法复杂度双重约束。
加载本节练习…
下一课 →数据结构