前言
目前主流的操作系统都实现了虚拟内存的技术,虚拟内存能突破物理内存的大小限制,使程序可以操作大于实际物理内存的空间,而linux的swap机制,就是虚拟内存技术的自然延伸:当系统的物理内存不够用时,要将物理内存中的部分数据写到磁盘上的swap分区,这部分内存区域通常是访问频率最低的一块区域,而不会影响比较「忙」的内存区域,当系统再次访问写到swap分区上的数据时,又需要将swap分区中的数据读到内存中。
由于swap的实现原理,使得它成为系统性能的一把双刃剑:一方面能突破物理内存大小的限制,使用有限的物理内存完成更多的工作;另一方面由于物理内存和磁盘swap分区间的数据交换,可能导致频繁的磁盘IO,严重影响系统性能。
控制文件系统缓存占用的物理内存
内核文件系统缓存是占用物理内存的一个大头,因为linux系统会尽最大可能使用物理内存,将磁盘中的数据尽可能的缓存到文件系统缓存中,这样大大提高了文件系统的性能,但是可能导致占用了本该其他实体可以占用的物理内存,最突出的问题就是和用户态进程争用内存,导致用户态内存出现swap。
内核的内存回收策略:当内存不足时,内核会进行页面回收,回收的目标页面分为2种,分别是文件页(file cache)和匿名页(anonymous cache)。文件页:和外部存储设备上的某个文件相对应,例如磁盘文件对应的page cache;匿名页,其内容不来自于外部存储设备,例如用户进程中的堆栈,回收匿名页会出现swap,因为页面里的数据不在文件里,要换出内存则必须swap。
大致看了一下linux 3.6.13的vmscan.c源文件,其中涉及到内存回收策略的部分主要在
get_scan_count()函数里,具体策略如下:
-
如果禁止swap或swap分区大小为0,优先回收文件页,不会swap,但是一般系统都不会这么设置,这样相当于完全禁用了swap
-
如果进行的不是全局页面回收(全局页面回收一般指cgroup资源限额引起的页回收),且swappiness设为0,优先回收文件页,不会导致swap
-
如果进行链表扫描前设置的priority(简单理解成一个系统参数,这个值决定一次扫描多少链表元素)为0,且swappiness非0,则等概率回收文件页和匿名页,如果回收匿名页会导致swap
-
如果是全局页回收,并且当前空闲内存和文件页的大小总和小于系统的high watermark(high watermark简单理解成系统的一个参数,根据/proc/sys/vm/min_free_kbytes计算的一个值),则进行匿名页回收,会发生swap
-
如果文件页的惰性链表容量较大(不论是文件页还是匿名页,内部又区分了2个链表:活动链表和惰性链表,频繁访问的页面在活动链表,非频繁访问的页在惰性链表,所以实际上有4个链表管理文件页和匿名页:文件页活动链表,文件页惰性链表,匿名页活动链表,匿名页惰性链表),优先回收文件页面,不会导致swap。
-
其他场景下,根据/proc/sys/vm/swappiness参数确定是优先回收文件页还是匿名页,回收匿名页会导致swap。 swappiness越大,释放匿名页的概率越高,如果想优先释放文件页,swappiness的值适当调小一些。
我们可以查看系统的页面使用情况:
cat /proc/meminfo
......
Active: 5392824 kB //活动链表总内存大小
惰性链表总内存大小
Inactive: 1766584 kB //惰性链表总内存大小
Active(anon): 4414968 kB //活动链表中匿名页总内存大小
Inactive(anon): 806108 kB //惰性链表中匿名页总内存大小
Active(file): 977856 kB //活动链表中文件页总内存大小
Inactive(file): 960476 kB //惰性链表中文件页总内存大小
综合分析上述策略,在文件系统缓存(page cache/buffer cache)还未释放完的情况下,内核也可能会回收用户进程映射的页面,导致swap,也就是前面说的文件系统缓存挤占了用户进程的内存。
在实际开发中,有2种比较简便的方法来控制文件系统缓存占用的物理内存:
-
尽量不要在本地做磁盘IO,磁盘IO通过网络发包的方式转到其他机器集中记录。例如业务进程记录日志,只在本地磁盘记录很少出现的严重错误,大批量的日志通过网络发包的方式发送到远程的日志中心集中记录。
-
无法避免在本地做磁盘IO的,合理设置linux系统的磁盘参数,避免文件系统缓存占用过多内存。例如 /proc/sys/vm/dirty_ratio参数,设置的过大,会导致更多的物理内存用来作为文件系统缓 存。/proc/sys/vm/swapiness参数,设置的越大,系统发生swap交换的概率越高。linux系统的磁盘参数设置是个细活,很难给一个各种场景都通吃的参数设置,只能根据实际业务场景去不断调优。
降低关键地址空间swap的可能性
所谓关键地址空间,就是CPU在访问时,其必须位于物理内存中,否则从磁盘中装载到物理内存中会影响性能。
内核在选择要swap出去的物理内存页时,是有选择性的。内核维护了2个链表,活动链表和惰性链表,活动链表下的内存被访问的频率高,惰性链表下的内存被访问的频率低。内核会定期检测活动链表和惰性链表中内存的访问频率,每块内存根据访问频率在两个链表间切换。当内存不足需要swap时,内核优先选择惰性链表下的内存swap出去。
降低关键地址空间swap的可能性,主要有2种方法:
- 使用mlock/mlockall函数(具体用法参考附录2),将进程地址空间锁定到物理内存。这种做法风险很大(如果进程出现内存泄露,锁定到物理内存中的地址空间越来越多,最后会出现OOM(Out Of Memory)),一般只对共享内存使用mlock,因为共享内存是典型的关键地址空间,也许平时都不访问里面的数据,一旦需要时,希望能立即从物理内存中读到。
- 将关键地址空间对应的物理内存维持在活动链表中,例如使用测试工具每秒发一个请求包,触发关键地址空间的指令被执行,数据被访问。降低关键地址空间swap也有个前提,机器的物理内存至少要能保证关键地址空间能常驻内存,并且还能剩余一部分容纳非关键地址空间。这样关键地址空间常驻内存而不被swap,非关键地址空间在在物理内存和磁盘间swap倒腾,基本不会对系统性能造成大的影响,也降低了机器成本。
关于可以被交换到swap分区上的页的说明:
- 类别为MAP_ANONYMOUS的匿名页,没有关联到文件(或属于/dev/zero的一个映射)。例如:可以是进程的栈,或是使用mmap匿名映射的内存区。
- 进程的私有映射,用于映射修改后不向底层块设备回写的文件,例如:使用MAP_PRIVATE标志创建的映射。
- 所有属于进程堆以及使用malloc分配的页(malloc又调用brk系统调用或匿名映射)。
- 用于实现进程间通信机制的页面,例如共享内存。
除此之外的其他内存页,都不会被交换到swap分区。
避免内存泄露
生产环境中,如果发生内存泄漏,在开启swap时,会导致频繁的磁盘io,以至于调用超时;但如果系统关闭了swap功能,一旦内存泄露把内存全部耗尽,就会触发linux系统的OOM机制,完全没有swap机制提供的缓冲时间去处理,将导致更严重的生产事故。 关于内存泄漏的原因及定位,其他地方都有很详细的解释,此处不做赘述。笔者在此指出一个常被忽略的一个原因:
如果频繁地进行malloc/free, new/delete操作小块内存空间,会导致堆内将产生越来越多不可用的内存碎片,无法被系统回收,实际效果等同于内存泄露。
对于使用java开发的读者,定位内存泄漏则会方便许多:通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们。对比正常清空与非正常情况的内存快照,分析多出了哪些对象。
在生产环境中,线上问题的及时处理是重中之重,当我们发现系统的物理存储占用越来越多,但是一时无法定位到内存泄露的原因时,可以通过定时重启进程的方式释放内存,避免系统进入swap甚至OOM状态。
合理设置socket缓冲区大小
内核socket缓冲区也总是位于物理内存中,不会swap,但是我们可以量体裁衣,合理设置socket缓冲区的大小,一般socket缓冲区设置几MB就够用了,如果设置的过大,也会占用本该其他实体可以占用的物理内存,触发不合理的swap。
相关参数
转载自bisonliao大牛开发的课程“编写高性能服务程序(磁盘IO篇)”的参考资料
/proc/sys/vm/dirty_ratio
这个参数控制文件系统的文件系统写缓冲区的大小,单位是百分比,表示系统内存的百分比,
表示当写缓冲使用到系统内存多少的时候,开始向磁盘写出数据。增大之会使用更多系统内存
用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,
应该降低其数值。
/proc/sys/vm/dirty_background_ratio
这个参数控制文件系统的pdflush进程,在何时刷新磁盘。单位是百分比,表示系统内存的百分
比,意思是当写缓冲使用到系统内存多少的时候,pdflush开始向磁盘写出数据。增大之会使用
更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的
写入场合时,应该降低其数值。
/proc/sys/vm/dirty_writeback_centisecs
这个参数控制内核的脏数据刷新进程pdflush的运行间隔。单位是 1/100 秒。缺省数值是500,
也就是 5 秒。如果你的系统是持续地写入动作,那么实际上还是降低这个数值比较好,这样可
以把尖峰的写操作削平成多次写操作。如果你的系统是短期地尖峰式的写操作,并且写入数据
不大(几十M/次)且内存有比较多富裕,那么应该增大此数值。
/proc/sys/vm/dirty_expire_centisecs
这个参数声明Linux内核写缓冲区里面的数据多“旧”了之后,pdflush进程就开始考虑写到磁盘
中去。单位是 1/100秒。缺省是 30000,也就是 30 秒的数据就算旧了,将会刷新磁盘。对于
特别重载的写操作来说,这个值适当缩小也是好的,但也不能缩小太多,因为缩小太多也会导
致IO提高太快。 当然,如果你的系统内存比较大,并且写入模式是间歇式的,并且每次写入
的数据不大(比如几十M),那么这个值还是大些的好。
/proc/sys/vm/vfs_cache_pressure
该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据
pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低
于100,将导致内核倾向于保留directory和inode cache;增加该值超过100,将导致内核倾向
于回收directory和inode cache。
/proc/sys/vm/min_free_kbytes
该文件表示强制Linux VM最低保留多少空闲内存(Kbytes)。
/proc/sys/vm/nr_pdflush_threads
该文件表示当前正在运行的pdflush进程数量,在I/O负载高的情况下,内核会自动增加更多的pdflush进程。
/proc/sys/vm/overcommit_memory
该文件指定了内核针对内存分配的策略,其值可以是0、1、2。
0:表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请
允许;否则,内存申请失败,并把错误返回给应用进程。
1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
2:表示内核允许分配超过所有物理内存和交换空间总和的内存(参照overcommit_ratio)。
/proc/sys/vm/overcommit_ratio
该文件表示,如果overcommit_memory=2,可以过载内存的百分比,通过以下公式来计算系统整体可用内存。 系统可分配内存=交换空间+物理内存*overcommit_ratio/100
/proc/sys/vm/page-cluster
该文件表示在写一次到swap区的时候写入的页面数量,0表示1页,1表示2页,2表示4页。
/proc/sys/vm/swapiness
该文件表示系统进行交换行为的程度,数值(0-100)越高,越可能发生磁盘交换。