本文是一篇个人系统学习笔记的系统化整理。起点并不是“高深理论”,而是一个非常生活化、却足以引出整套计算机体系的问题:
“我们在代码里申请的内存,最后到底对应什么实物?”
在不断追问这个问题的过程中,我逐渐把 CPU、内存、虚拟内存、操作系统、文件系统、Android 存储模型等零散知识,串成了一条相对完整的认知链路(BY GPT)。
序言:从一个“return 局部对象”的疑问说起
起因是室友(C/C++ 背景)的一次提问:
“为什么不能在函数里 return 一个局部对象?”
我当时的第一反应是:当然不行啊,出了作用域就没了。
于是给她简单讲了一下 Java 里最基础的内存模型:
- 栈(Stack)
- 线程私有
- 函数调用创建,返回即销毁
- 局部变量、参数、返回地址
- 堆(Heap)
new出来的对象- 通过“引用”建立联系
- Java 中由 JVM 回收,C/C++ 需要手动
free / delete
- 方法区(或元数据区)
- 类信息、常量、静态变量
- 从抽象意义上更接近“特殊的堆”
在这个层面,我们很快达成了共识。
但紧接着,她问了一个更本质的问题:
“那我们代码里申请的这些内存,对应的实物到底是什么?是 RAM 吗?”
我当时正好在补操作系统基础,自以为理解了虚拟内存,于是回答:
“严格来说是虚拟内存;如果非要对应实物,那是主存 + 磁盘,由操作系统调度。”
她一脸懵逼。
后来我意识到:不是她没懂,而是我自己的理解还没有真正“打通”。
于是,我决定从最基础的计算机组成开始,重新梳理一遍。
一、计算机硬件组成:我们眼睛能看到的那一层
1.1 基本硬件模块
一台计算机,从硬件角度看,通常包括:
- CPU
- 算术逻辑单元(ALU)
- 控制单元(CU)
- 寄存器
- Cache(多级缓存)
- 内存管理单元(MMU)
- 内存储器
- RAM(主存):断电易失,与 CPU 直接交互
- ROM:只读存储,存放 BIOS / Bootloader
- 外存储器
- 硬盘、SSD 等
- 输入 / 输出设备
- 键盘、显示器、网络设备、打印机等
这些都是物理存在、肉眼可见的组件。
1.2 CPU 如何与内存交互
CPU 与 RAM 之间,通过三类总线通信:
- 地址总线:指定访问位置
- 数据总线:传输数据
- 控制总线:读写、时序等控制信号
在数据层面,CPU 访问的内容只有两类:
- 指令
- 数据
而“指令”,就引出了指令集与语言的问题。
二、指令、汇编与高级语言
2.1 汇编语言:人类能理解的指令集表示
早期的汇编语言,本质上是:
机器指令的文本化表示
例如 8086 的指令集。
需要澄清的一点是:
- 汇编代码确实是文本文件(ASCII / UTF-8 编码)
- 但字符编码只是存储形式
- 指令的语义由:
- 汇编器
- CPU 架构 共同决定,与 ASCII 本身无关
正如《CSAPP》中所说:
源代码是文本文件,其他一切都是可执行文件。
2.2 编译型与解释型语言(传统视角)
传统上,我们会这样分类:
- 编译型语言(C / C++)
- 源码 → 编译 → 可执行文件(ELF / exe)
- 解释型语言(Python)
- 源码 → 解释器 → 执行
这在理解早期语言模型时非常有用,但在 Java、Android、JavaScript 出现后,已经不够准确。
我曾看到一个对“解释型语言”的定义,很有启发性:
不能被 CPU 直接执行,必须依赖运行时环境(一个程序来运行另一个程序)
这也解释了为什么:
- JVM、Python 解释器、ART
- 本身都大量使用 C/C++ 编写
因为:
解释器本身,必须足够快。
2.3 Java:编译 + 虚拟机执行的混合模型
Java 的执行路径是:
.java
↓ javac
.class(字节码)
↓
JVM(解释 / JIT / AOT)
↓
CPU
因此更准确的说法是:
Java 是“面向虚拟机的编译语言”
它既不是“不编译”,也不是“直接编译成机器码”。
三、操作系统:连接软件与硬件的中枢
3.1 从功能角度看 OS
- 对应用程序而言
- 提供执行环境
- 提供 IO、网络等服务
- 管理和终止进程
- 对内核而言
- CPU 调度
- 内存管理
- 设备管理
- 文件系统
一句我非常喜欢的总结是:
OS 将 CPU 虚拟化为进程,将内存虚拟化为地址空间,将磁盘虚拟化为文件。
3.2 虚拟内存:回答“内存对应什么实物”的关键
虚拟内存的目标是:
让每个进程都“以为”自己拥有连续、完整的内存空间。
实际上:
- 虚拟地址 → MMU → 物理地址
- 数据可能在:
- RAM
- 磁盘(swap / 文件映射)
因此:
我们在代码中申请的“内存”,并不直接等价于一块固定的 RAM 芯片区域。
四、内存层次结构:为什么有 Cache
从 CPU 视角看,存储是分层的:
- 寄存器(最快,最小)
- L1 / L2 / L3 Cache
- 主存(RAM)
- 磁盘(最慢,最大)
这是用空间换时间的经典设计。
五、Java 内存与并发中的“可见性”问题
Java 的内存模型,可以粗略理解为:
- 主内存
- 每个线程的工作内存(缓存)
示例代码中:
static boolean run = true;
子线程不退出的原因,并不是“线程没看到修改”,而是:
- JIT / CPU 缓存导致读取的是旧值
这正是 volatile、synchronized 存在的意义。
六、磁盘、分区、挂载与文件系统
6.1 为什么要 mount
磁盘在未挂载前:
- 只是“原始二进制数据”
挂载之后:
- 通过文件系统
- 才能看到目录、文件、inode 等概念
6.2 inode 与 fd
- inode:文件系统内部的数据结构
- fd:VFS 层暴露给应用的操作句柄
应用程序:
只和 fd 打交道,不关心底层文件系统实现。
这正是 OS 的抽象能力。
七、Android 存储模型(概览)
从 Android 4.4 到 11+:
- internal / external 的语义不断变化
- Scoped Storage 成为默认模型
- 权限控制通过 mount namespace + FUSE 实现
这些变化,本质上仍然是:
在 Linux 文件系统抽象之上,进一步做安全与隔离。
八、抽象与缓存:贯穿整个系统的思想
《CSAPP》中有一句非常经典的总结:
- 文件:I/O 设备的抽象
- 虚拟内存:程序存储器的抽象
- 进程:正在运行程序的抽象
- 虚拟机:整个计算机的抽象
而缓存:
- 小到一个 HashMap
- 大到 CPU Cache、页缓存
都是同一种思想的不同尺度体现。
结语
回到最初的问题:
我们写代码时申请的内存,对应什么实物?
答案已经不再是某一块具体的 RAM,而是:
操作系统 + 硬件 + 抽象机制共同构建的一套“看起来像内存”的系统。
如果这篇文章能让你对“内存”“存储”“操作系统”的边界稍微清晰一点,那它的目的就达到了。
有任何错误或不严谨之处,欢迎指正 🙏