一、虚拟内存和物理内存的生动比喻
想象你有一个书桌和一个图书馆:
- 物理内存(RAM) 就像你的书桌,桌面空间有限,能放的书和资料有限。
- 虚拟内存 就像你在图书馆的借书卡,虽然桌面空间有限,但你可以通过借书卡从图书馆借来很多书,感觉自己“拥有”了比桌面更大的空间。
具体说明:
- 你的电脑或手机运行程序时,程序需要用内存来存放数据和代码。
- 物理内存(书桌)是实际存在的快速存储空间,访问速度快,但容量有限。
- 虚拟内存(借书卡)是操作系统给每个程序分配的“假象”内存空间,程序看到的是一个很大的连续空间,不必关心物理内存的大小和分布。
- 当程序需要的数据不在书桌上时,操作系统会从图书馆(硬盘或压缩内存)取书放到书桌上,或者把书桌上的书放回图书馆腾出空间。
二、虚拟内存是如何升高和降低的?
1. 虚拟内存“升高”——为什么虚拟内存会变大?
继续用书桌和图书馆的比喻:
- 当你开始做一个项目(运行一个程序),你会把需要的书(内存页)放到书桌上。
- 如果项目变复杂了,你需要查阅更多的书,桌面上的书就会越来越多。
- 这时,你的“借书卡”允许你“借”更多的书,虚拟内存空间就“升高”了。
在系统层面,虚拟内存升高的原因包括:
- 程序申请更多内存:调用
malloc
、new
或mmap
等接口申请内存。 - 加载更多模块或库:程序运行时加载更多动态库或资源文件。
- 内存映射文件增多:映射大文件到内存。
- 内存泄漏:程序没有释放不再使用的内存,导致虚拟内存持续增长。
2. 虚拟内存“降低”——虚拟内存是如何减少的?
回到比喻:
- 当你完成某个项目或不再需要某些书时,你会把书收起来,腾出桌面空间。
- 你也可能把不常用的书放回图书馆,保持桌面整洁。
- 这样,桌面上的书(虚拟内存)数量就减少了。
在系统层面,虚拟内存降低的方式包括:
- 程序释放内存:调用
free
、munmap
等接口释放内存。 - 垃圾回收:Java、Kotlin等语言的GC机制回收无用对象。
- 内存映射解除:调用
munmap
解除文件映射。 - 系统回收:操作系统通过Low Memory Killer杀死后台进程,释放内存。
- 内存压缩和交换:使用zRAM压缩内存,或者将部分内存页换出到磁盘(传统swap)。
三、总结
方面 | 书桌和图书馆比喻 | 系统层面对应 |
---|---|---|
物理内存 | 书桌,有限的实际空间 | RAM,实际的高速内存 |
虚拟内存 | 借书卡,感觉拥有比书桌更大的空间 | 操作系统给程序的虚拟地址空间 |
虚拟内存升高 | 需要更多书,桌面上放更多书 | 程序申请更多内存,加载更多资源 |
虚拟内存降低 | 收起书,腾出桌面空间 | 程序释放内存,系统回收,内存映射解除 |
我继续帮你深入讲解虚拟内存升高和降低的具体机制,并结合一些技术细节和实际操作,帮助你更全面理解。
四、虚拟内存升高的具体机制
虚拟内存的“升高”本质上是进程的虚拟地址空间被分配和使用的过程。具体来说:
1. 进程申请内存
-
堆内存增长
程序通过malloc
、new
等接口申请堆内存,操作系统会在虚拟地址空间中为堆分配新的区域。- 堆的增长通常是连续的,操作系统通过
brk
或mmap
系统调用扩展堆空间。 - 这会使虚拟内存峰值升高。
- 堆的增长通常是连续的,操作系统通过
-
栈内存增长
每个线程有自己的栈,栈空间会根据函数调用深度动态增长。- 栈的增长也会占用虚拟内存空间。
-
内存映射(mmap)
程序通过mmap
映射文件或匿名内存,映射区域会占用虚拟地址空间。- 例如加载动态库、映射大文件、分配匿名内存等。
- 这部分虚拟内存通常不连续,且可能很大。
2. 资源加载
- 动态库加载
程序运行时加载的.so文件会映射到虚拟内存。 - 大对象和缓存
比如图片、视频缓冲区等大对象,会占用大量虚拟内存。
3. 内存泄漏
- 程序未释放不再使用的内存,导致虚拟内存持续增长。
- Native层泄漏尤为严重,因为GC无法管理。
五、虚拟内存降低的具体机制
虚拟内存降低,意味着进程释放了部分虚拟地址空间,具体方式包括:
1. 释放堆内存
- 程序调用
free
或delete
释放堆内存。 - 如果释放的是堆尾部的内存,操作系统可能通过
brk
收缩堆空间,减少虚拟内存。 - 对于中间区域释放,内存会被标记为可用,但虚拟地址空间大小不一定减少。
2. 解除内存映射
- 调用
munmap
解除文件或匿名内存映射,释放对应虚拟地址空间。 - 这通常能显著降低虚拟内存峰值。
3. 垃圾回收(GC)
- Java/Kotlin等语言的GC回收无用对象,释放堆内存。
- GC后,部分内存会被释放回操作系统,虚拟内存峰值降低。
4. 进程退出或被杀死
- 进程结束时,操作系统回收所有虚拟内存。
- LMK杀死后台进程,释放内存压力。
六、虚拟内存升高和降低的系统视角
操作类型 | 系统调用/机制 | 虚拟内存变化 | 备注 |
---|---|---|---|
申请堆内存 | brk 、mmap | 虚拟内存升高 | 堆空间扩展 |
释放堆内存 | free + brk 收缩 | 虚拟内存降低(仅尾部收缩) | 中间释放不一定减少虚拟内存 |
映射文件 | mmap | 虚拟内存升高 | 动态库、资源文件映射 |
解除映射 | munmap | 虚拟内存降低 | 释放映射区域 |
线程栈增长 | 自动增长 | 虚拟内存升高 | 递归调用、深度调用导致栈增长 |
线程栈释放 | 线程结束 | 虚拟内存降低 | 线程退出释放栈空间 |
GC回收 | JVM GC | 虚拟内存降低 | 释放Java堆内存 |
进程结束 | 进程退出 | 虚拟内存归零 | 操作系统回收所有资源 |
七、实际操作示例
1. 查看进程虚拟内存使用
adb shell cat /proc/<pid>/status | grep VmPeak
VmPeak
表示进程历史最高虚拟内存使用量。
2. 观察内存映射
adb shell cat /proc/<pid>/maps
- 查看进程当前所有虚拟内存映射区域。
3. 申请和释放内存示例(C语言)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Initial brk: %p\n", sbrk(0));
// 申请10MB堆内存
void* p = malloc(10 * 1024 * 1024);
printf("After malloc brk: %p\n", sbrk(0));
// 释放内存
free(p);
printf("After free brk: %p\n", sbrk(0));
return 0;
}
- 你会发现
brk
指针(堆顶)在malloc
后升高,free
后不一定降低,除非调用malloc_trim
或程序主动收缩堆。
八、总结
- 虚拟内存是程序看到的地址空间,物理内存是实际的硬件内存。
- 虚拟内存升高主要由程序申请内存、映射文件、加载库等行为引起。
- 虚拟内存降低主要由程序释放内存、解除映射、GC回收和进程退出引起。
- 虚拟内存峰值高不一定马上导致物理内存耗尽,但持续升高会增加OOM风险。
- 合理管理内存申请和释放,及时解除映射,是控制虚拟内存峰值的关键。