一、Linux内存是怎么工作的?
-
什么是内存映射?
-
虚拟内存地址到物理内存地址的映射机制
-
页表 -> 存储在cpu的内存管理单元MMU/TLB,通过MMU通过TLB缓存页表,页表页大小4kb一页
-
如果页表使用线性结构,整个虚拟地址空间需要占用大量的页表项 * 如何避免页表项过多 * 多级页表 -> 本质上是个多叉树 * 仅保留使用中的页,因此可以节省多数未使用的页表项 * 一级 -> 二级 -> 三级 -> 偏移量 => 物理地址 * 转换成线性结构: 1级 * 2级 * 3级 * 大页 -> 2MB、1GB
-
-
什么是虚拟内存?
-
进程独享的内存地址空间
-
内核空间 -> 内核态可访问
-
用户空间 -> 用户态可访问
-
-
地址空间 -> 32位/64位范围
-
虚拟内存 >> 物理内存
-
分段
-
只读 -> 常量,代码
-
数据 -> 全局变量
-
堆 -> 动态分配内存
-
文件映射 -> 动态库,共享内存(进程共享)
-
栈 -> 局部变量,函数调用上下文
-
-
-
-
-
如何分配和回收内存?
-
动态分配和释放
-
堆 -> 适合小对象
-
brk()/free() 系统调用
-
可重复使用,释放后不会立即归还系统
-
容易导致内存碎片
-
-
文件映射 -> 适合大对象
-
mmap()/unmap() 系统调用
-
每次调用都会触发缺页异常
-
释放后立刻归还系统
-
-
物理内存分配管理机制
-
页 -> 按页分配
-
SLAB -> 适合小对象
-
其他
-
-
-
系统回收
-
回收缓存(LRU)
-
swap -> 交换不常用的内存到磁盘
-
可能导致性能严重下降,例如ES等对性能要求严格的服务会禁用swap
-
-
OOM -> oom_score 打分机制
-
内存高,cpu占用低的进程oom_score较大
-
手动打分 -> echo -16 > /proc/$(pidof sshd)/oom_adj
-
查看OOM日志 -> dmesg |grep -E ‘kill|oom|out of memory‘
-
-
-
-
内存指标
-
free
-
total -> 总内存
-
used -> 已使用内存,包括share
-
free -> 未使用内存(排除了buff/cache/share
-
share -> 共享内存(共享 + 程序代码段 + 动态链接库)
-
buff/cache -> buffers(磁盘缓存) + cached(页缓存)+ SReclaim(可回收Slab)
-
slab -> 管理内核中的小块内存
-
-
available -> 可用内存
-
-
top
-
VIRT -> 虚拟内存(申请过的,即使没有实际分配物理内存)
-
内核线程该指标=0
-
-
RES -> 常驻内存 (实际使用的物理内存,不包括swap)
-
SHR -> 共享内存(共享 + 程序代码段 + 动态链接库)
-
%MEM -> 物理内存占用 / 总内存
-
-
二、怎样理解内存中的Buffer和Cache?
Buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据(通常不会特别大)。Cached是从磁盘读取文件的页缓存,用来缓存从文件中读取的数据。Slab包括可回收和不可回收两部分。
三、内存泄漏,如何定位和处理?
虚拟内存分布从低到高分别是只读段,数据段,堆,内存映射段,栈五部分。其中会导致内存泄漏的是:
堆: 由应用程序自己来分配和管理,除非程序退出这些堆内存不会被系统自动释放。 内存映射段:包括动态链接库和共享内存,其*享内存由程序自动分配和管理 内存泄漏的危害比较大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。 内存泄漏不断累积甚至会耗尽系统内存。
实验 如何检测内存泄漏 预先安装systat,docker,bcc
sudo docker run --name=app -itd feisky/app:mem-leak sudo docker logs app vmstat 3 可以看到free在不断下降,buffer和cache基本保持不变。说明系统的内存一致在升高。但并不能说明存在内存泄漏。此时可以通过memleak工具来跟踪系统或进程的内存分配/释放请求。
/usr/share/bcc/tools/memleak -a -p $(pidof app) 从memleak输出可以看到,应用在不停地分配内存,并且这些分配的地址并没有被回收。通过调用栈看到是fibonacci函数分配的内存没有释放。定位到源码后查看源码来修复增加内存释放函数即可。
内存性能指标
系统内存指标
-
已用内存/剩余内存
-
共享内存 (tmpfs实现)
-
可用内存: 包括剩余内存和可回收内存
-
缓存:磁盘读取文件的页缓存,slab分配器中的可回收部分
-
缓冲区: 原始磁盘块的临时存储,缓存将要写入磁盘的数据
进程内存指标
-
虚拟内存: 5大部分
-
常驻内存: 进程实际使用的物理内存,不包括Swap和共享内存
-
共享内存: 与其他进程共享的内存,以及动态链接库和程序的代码段
-
Swap内存: 通过Swap换出到磁盘的内存
缺页异常
-
可以直接从物理内存中分配,次缺页异常
-
需要磁盘IO介入(如Swap),主缺页异常。 此时内存访问会慢很多
根据不同的性能指标来找合适的工具:
内存分析工具包含的性能指标:
如何迅速分析内存的性能瓶颈
通常先运行几个覆盖面比较大的性能工具,如free,top,vmstat,pidstat等
-
先用free和top查看系统整体内存使用情况
-
再用vmstat和pidstat,查看一段时间的趋势,从而判断内存问题的类型
-
最后进行详细分析,比如内存分配分析,缓存/缓冲区分析,具体进程的内存使用分析等
常见的优化思路:
-
最好禁止Swap,若必须开启则尽量降低swappiness的值
-
减少内存的动态分配,如可以用内存池,HugePage等
-
尽量使用缓存和缓冲区来访问数据。如用堆栈明确声明内存空间来存储需要缓存的数据,或者用Redis外部缓存组件来优化数据的访问
-
cgroups等方式来限制进程的内存使用情况,确保系统内存不被异常进程耗尽
-