内存的性能瓶颈主要集中在两点,一个是内存不足,一个是没有充分的利用buffer/cache 。
这里先介绍内存资源不足吧,毕竟应用程序不在 buffer/cache 里读写的可能性不大,除非程序员自己设置直接I/O。否则至少也用的是缓存I/O。当然一些动态链接库也有自己的缓存。
内存回收
当有内存资源不足的时候,就会导致内存回收,也就是释放掉回收的内存,比如buffer/cache 就属于可回收内存,他们在内存管理中叫做文件页(File-backend Page)。大部分的文件页都可以直接回收,以后有需要的时候,再从磁盘重新读取,而那些已经被应用程序改过的,暂时还没有写入磁盘的数据,在内存管理中就叫脏页。需要先写到磁盘中,内存才能得到释放。
当然这些脏页一般通过2种方式写入磁盘,
- 应用程序中的fsync
- 内核线程pdflush
除了文件页之外,另一种就是匿名页(Anonymous Page),这些可以是应用程序动态分配的堆内存,这些可以被回收么?当然不能直接回收,要不程序再次访问自己申请的内存时,内存资源突然不见,这可怎么整。但这些内存很少被访问,所以会被放到磁盘中。当再次访问时,会发生主缺页异常(major page faults),再重新加载进内存中。这就是linux中的swap机制。
swap原理
swap说白了就是把一块磁盘空间或者一个本地文件当成内存使用。他包含了换入和换出的两个过程。
- 换出:就是把进程暂时不用的内存数据存储到磁盘上,并释放这些数据占用的内存。
- 换入:进程再次访问内存时,从磁盘重新读入内存中。
也就是linux在内存回收方面,做了两件事。
- 直接内存回收:回收一部分缓存,进而满足新内存的申请请求。
- 定期内存回收:内核通过kswapd0定期回收内存。
什么时候会定期内存回收
定期回收内存是由内核进程kswapd0来进行的。为了衡量内存的使用情况,kswapd0定义了三个阈值。
内存使用阈值
kswapd0是根据三个阈值进行内存的回收操作的。
- 当剩余内存小于页最小阈值时,只有内核才可以分配新的内存。
- 当剩余内存大于页最小阈值并小于页底阈值时,说明此时内存压力巨大,此时wswapd0会一直回收内存,直到内存大于高阈值。
- 当剩余内存大于页底阈值并小于页高阈值,说明内存存在一定的压力,但还算满足新的内存申请。
- 当剩余内存大于页高阈值时,说明内存无压力。
当剩余内存小于页底阈值就会触发内存回收,这个页底阈值一般是通过内核选项 /proc/sys/vm/min_free_kbytes 设置。
$ cat /proc/sys/vm/min_free_kbytes
67584
而剩余的两个阈值是由页底阈值计算生成的。
pages_low = pages_min*5/4
pages_high = pages_min*3/2
在 /proc/zoneinfo中可以看到当前的三个阈值
$ cat /proc/zoneinfo
Node 0, zone DMA
pages free 3840
min 69
low 86
high 103
scanned 0
spanned 4095
present 3997
managed 3976
nr_free_pages 3840
nr_alloc_batch 17
nr_inactive_anon 21
nr_active_anon 8
nr_inactive_file 13
nr_active_file 0
这里的 min、low、high是内存页数的阈值。free是剩余内存页数。
nr_zone_active_anon 和 nr_zone_inactive_anon ,分别是活跃和非活跃的匿名页数。
nr_zone_active_file 和 nr_zone_inactive_file ,分别是活跃和非活跃的文件页数。
swap的回收偏向
上面只是说了什么时候会回收内存,但刚才也说到了,内存不够了,一种方式是释放文件页。另一种方式就是将匿名页写入到磁盘(swap)。
两种方式,linux更倾向于哪一种呢?
内核参数 /proc/sys/vm/swappiness 。是用来调整swap的积极性的,也就是说这个参数越高,内存的回收更倾向于swap机制,反则倾向于释放buffer/cache 。但这个参数设置为0 并不代表就不会使用swap了。
swap好吗?
实则根据业务场景,大部分应用都不需要开启swap的。在hadoop,es等java应用其实都是应该关闭swap的,因为java代码在gc时,会遍历所有用到的堆内存,如果这部分内存是在swap上,遍历操作可能就变成磁盘io了。
swap是针对小内存的一种优化,现在的线上服务器动辄128G内存,所以没必要开启swap。如果要开启的话,也是建议将 /proc/swappiness 设置一个较小的值,如10。
另一种场景下,kubernets集群一般会关闭swap。因为开启swap后,通过cgroups设置的内存上限会失效。
响应延迟敏感性的应用如果在开启swap的服务器上运行的话,可以使用库函数 mlock() 或者 mlockall() 锁定内存,阻止内存的换出和换进。
另一个就是numa架构,非一致性内存访问。这个最好也关掉,跨node访问也会造成严重的性能问题。
我的应用开始使用swap了?
既然swap会造成应用程序的性能问题,那么怎么知道我的某个具体的程序开始使用swap了?
$ pidstat -r 1
Linux 3.10.0-957.el7.x86_64 (vm1.centos7.com) 2019年04月05日 _x86_64_ (2 CPU)
09时21分41秒 UID PID minflt/s majflt/s VSZ RSS %MEM Command
09时21分42秒 0 10445 353.85 0.00 108380 1168 0.03 pidstat
pidstat -r 表示的就是单个应用程序的内存使用情况。
- minflt/s : 每秒次缺页错误次数(minor page faults),次缺页错误次数意即虚拟内存地址映射成物理内存地址产生的page fault次数。
- majflt/s : 每秒主缺页错误次数(major page faults),当虚拟内存地址映射成物理内存地址时,相应的page在swap中,这样的page fault为major page fault,一般在内存使用紧张时产生。
也就是说如果某个应用程序有majflt/s 了,表示其开始使用swap了。其性能当然也会下降。
作者:OOM_Killer
链接:https://www.jianshu.com/p/24550b83f390
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。