内存
内存管理是操作系统最核心的功能之一,主要用于存储系统和应用程序的指令、数据、缓存等。
1. 内存映射
我们购买电脑的考虑的一个重要参数就是内存,比方说,我的笔记本电脑的内存是 8 GB 的,而该内存通常指的是物理内存。物理内存也称为主存,大多数计算机的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存,所以内核给每个进程都提供了一个独立的虚拟地址空间,用于进程访问内存,更确切的说是虚拟内存。
虚拟内存地址空间被分为内核空间和用户空间,进程在用户态时,只能访问用户空间;只有进入内核态时,才允许访问内核空间内存。虽然每个进程的地址空间中都包含了内核空间,但实际上这些内核空间关联的都是相同的物理内存。所以每个进程切换内核态后,还是可以方便的访问内核空间内存。
实际并不是所有的内存都会分配物理内存,只有实际使用的虚拟内存才会分配物理内存,分配后的物理内存是通过内存映射管理的。
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存]碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。
内存映射,其实就是将虚拟内存地址映射到物理内存地址上,内核为每个进程都维护了一个页表,记录虚拟地址和物理地址的映射关系。当进程访问的虚拟地址在页表中不存在时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程的页表,最后返回用户空间,恢复进程的执行。
2. 如何查看内存的使用情况
在对内存有了基本的了解之后,我们应该如果查看系统的内存使用情况呢?通常可以使用 free
或者vmstat
工具来查看:
$ free
total used free shared buff/cache available
Mem: 1933076 309264 860000 5788 763812 1464292
Swap: 0 0 0
不过,free
显示的是整个系统的内存使用情况。如果你想查看进程的内存使用,可以使用 top
或者 ps
工具。
如何理解内存中的Buffer和Cache
在之前通过free
工具查看内存情况时,有一个指标buffer/cache
表示被使用的缓冲区和缓存之和, 我们可以使用 man free
命令查看 free
的文档,文档中对这两者的描述如下:
$ man free
buffers Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
-
buffers
: 内核缓冲区用到的内存,是对原始磁盘块的临时存储,也就是用来缓存磁盘数据,通常不会特别大。 -
Cache
: 页缓存和Slab
用到的内存,是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的文件数据。
注意:Buffer
和Cache
不但可以用在读请求,也会用在写请求中(例如写合并:将多次分散的、小的写入合并成单次大的写入)。
内存泄露了如何处理
对应用程序来说,动态内存的分配和回收,是既核心又复杂的一个逻辑功能模块。管理内存的过程中,也容易发生各种各样的事故,比如:
- 没正确回收分配后的内存,导致了泄漏。
- 访问的是已经分配内存边界外的地址,导致程序异常退出,等等。
1. 内存的分配和回收
用户空间内存包含多个不同的内存段,比如只读段、数据段、堆、栈以及文件映射段等。这些内存段是应用程序使用内存的基本方式。
栈内存由系统自动分配和管理,一旦程序运行超过这个局部变量的作用域,栈内存就会自动回收,所以不会产生内存泄漏。
堆内存由应用程序自己分配和管理,除非程序退出,否则并不会被系统自动释放,而是需要应用程序明确调用库函数(free()
) 进行释放,如果应用程序没有正确释放堆内存,就会造成内存泄漏。
只读段,包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以不会造成内存泄漏。
数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理,所以如果程序在分配后忘记回收,就会导致内存泄漏。
内存泄漏的危害极大,这些忘记释放的内存,不仅应用程序自己无法访问,系统也不能将它们再次分配给其他应用,不断累积甚至会耗尽系统内存。
虽然系统可能会通过OOM
机制杀死进程,但是在OOM
之前就已经引发了一连串的反应,导致严重的性能问题。比如无法分配内存给其他需要内存的进程;内存不足又会触发系统的缓存回收以及SWAP
机制,从而进一步导致IO
性能问题等。
2. 内存问题的定位和分析
我们可以通过 vmstat
观察空闲内存是否一直在变小,但是这并不能说明有内存泄漏,因为应用程序 运行中需要的内存也可能会增大。那怎么确定是不是内存泄漏呢?或者有没有简单的办法找出让内存增长的进程,并定位增长内存用在哪?
可以使用 memleak
工具跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况。
为什么系统的Swap变高了
系统的内存资源紧张时,会导致两种结果,内存回收和OOM
杀死进程。内存回收,就是系统释放可以回收的内存,比如缓存和缓冲区。它们在内存管理中,叫做内存页,大部分文件页,都可以直接回收,以后有需要从磁盘重新读取即可,而那些脏页,得先写入磁盘,才能进行内存释放。
脏页的刷新有两种方式,在应用程序中,通过系统调用fsync
将脏页同步到磁盘中;由内核线程pdflush
刷新脏页。
除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次访问的时候重新进行读取。除了文件页,应用程序动态分配的堆内存(匿名页)是不是也可以进行回收呢?
由于这些匿名页很可能会再次访问,所以不能直接回收,但是这些在分配之后很少访问的内存,似乎是一种资源的浪费,所以可以将这些暂时先存到磁盘中,释放内存给其他更需要的内存,这就是Swap
机制。Swap
把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘中读取即可。
Swap原理
swap
原理实际就是将一块磁盘空间当做内存来使用,即使服务器的内存不足,它也能通过另一个方式扩大服务器的内存,它包括换入和换出两个过程。
- 换出: 把进程暂时不使用的内存数据存储到磁盘中,并且释放这些数据占用的内存。
- 换入:在进程再次访问这些内存时,把它们从磁盘中读取到内存中来。
既然swap
是为了回收内存,那么什么情况下linux
会触发内存回收的动作呢?一个最容易想到的场景就是,有大块内存的分配请求,但是剩余内存不足,为了满足分配,会回收一部分内存,这个过程叫做直接内存回收。除此之外,linxu
还会通过一个内核线程(kswapd0
)定期回收内存, 为了衡量内存使用情况,linux
定义了三个内存阈值,分别为页最小阈值、页低阈值和页高阈值。该内核线程会定期扫描内存使用情况,然后根据内存阈值进行回收操作: - 剩余内存小于页最小阈值时,说明进程可用内存耗尽了, 只用内核才允许分配内存。
- 剩余内存在页最小阈值和页低阈值之间,说明内存压力过大,此时内核线程会回收线程,直到剩余内存大于页高阈值。
- 剩余内存在页低阈值和页高阈值之间,说明内存有一定压力,但还能接受内存分配的请求。
- 剩余内存大于页高阈值,说明内存毫无压力。
分析步骤
- 通过
sar
工具查看内存各个指标的变化情况,确定内存的分配情况。
$ sar -r -S 1
Linux 5.10.84-10.4.al8.x86_64 (iZ2ze46nbb45cm0vd4oujfZ) 07/22/2023 _x86_64_ (1 CPU)
07:09:43 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
07:09:44 PM 423456 1329504 1509620 78.09 168912 824284 1363628 70.54 650336 675748 248
07:09:43 PM kbswpfree kbswpused %swpused kbswpcad %swpcad
07:09:44 PM 0 0 0.00 0 0.00
- 如果缓冲区不断增大,通过
cachetop
命令查看哪些进程导致缓冲区增大。缓冲区属于可回收内存,内存不够用时应该会回收这部分内存,为什么可能会导致swap
升高呢?在swap
原理中有对linux
的内存阈值有过简单的介绍,可以通过watch -d grep -A 15 'Normal' /proc/zoneinfo
命令查看内存阈值、文件页、匿名页的大小变化。 - 也可以通过
smem --sort swap
查看使用swap
最多的进程,然后进行详细的剖析。
如何快准狠的找出系统内存问题
内存性能指标
为了分析内存的性能瓶颈,首先应该知道如何衡量内存的性能,也就是性能指标。
首先就是系统的内存使用情况,包括已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量等。
然后是进程内存使用情况,比如进程的虚拟内存、常驻内存、共享内存已经swap
内存等等。
第三个重要的指标就是 swap
使用情况,比如 swap
已用空间、剩余空间、换入和换出速度等。
内存性能工具
为了迅速定位内存问题,通常先运行几个覆盖面比较大的性能工具,比如 free
、top
、vmstat
、pidstat
等。
- 先用
free
和top
,查看系统的整体内存使用情况。 - 再用
vmstat
和pidstat
查看一段时间的趋势,从而判断内存问题的类型。 - 最后进行详细的分析,比如内存分配分析、缓存\缓冲区分析、具体进程的内存使用分析等。
小结
内存调优最重要的就是,保证应用程序的热点数据放到内存中,并尽量减少换页和交换。常见的优化思路就这么几种。
- 最好禁止
swap
。如果要必须开启的话,降低swappiness
的值,减少回收时swap
的使用倾向。 - 减少内存的动态分配。比如,可以使用内存池,大页等。
- 尽量使用缓存和缓冲区访问数据。比如使用堆栈明确声明内存空间,来存储需要缓存的数据,或者使用外部缓存组件,优化数据访问。
- 使用
cgroups
等方式限制进程的内存使用情况,确保系统内存不会被异常进程耗尽。 - 通过
/proc/pid/oom_adj
调整核心线程的oom_score
。这样,可以保证即使内存紧张,核心应用也不会被OOM
杀死。