前言

作为一个性能优化学习者,内存相关的知识是一定要了解的。那么为了咱们提升自己便于日后娶走嘉然 (不是),首先要在内心有概念的就是swap、swappiness以及kswapd。

SWAP是什么

从功能上讲,交换分区主要是在内存不够用的时候,将部分内存上的数据交换到swap空间上,以便让系统不会因内存不够用而导致oom或者更致命的情况出现。

实际上就是把一块磁盘空间或者一个本地文件当成内存来使用。有换入和换出两个动作
换出:把进程暂时不用的额内存存到磁盘中,并释放这些数据占用的内存
换入:进程再次访问这些内存的时候,从磁盘读取数据到内存。
最早的方案就是从flash中分出一块区域来用作swap分区,当内存紧张的时候就会唤醒kswapd进程将内存数据交换到swap分区,而不是杀掉,切换回来时就可以直接将这一部分数据恢复至内存当中,节省开销

这个技术的好处在于swap分区所占用的内存已经被zram技术压缩过了,所以在内存中需要占用50M内存空间的数据只需要占用20M,这样swap分区就可以存放更多后台暂时不用的应用程序,变相扩展了内存的大小。

zram大小

zram的大小一般设置为内存大小的50%,在小内存的机器上一般会建议配置为更大的参数,一般在65%左右。

所以,当内存使用存在压力,开始触发内存回收的行为时,就可能会使用swap空间。

什么情况下会触发内存回收

一、在系统请求内存使用时,如果出现内存不足的情况,将会进行内存回收,swap内存会被回收,一般来说内核需要保证足够free的内存空余。

二、内核线程kswapd0,会定期回收内存。什么情况下回收有三个内存阈值,pages_min,pages_low,pages_high。页低阈值通过内核选项/proc/sys/vm/min_free_kbytes来配置页最小阈值,其他两个参数通过最小阈值计算生成。

会有几个配置的属性进行回收判断

属性含义
dalvik.vm.heapstartsizejava堆的初始大小
dalvik.vm.heapgrowthlimit堆的增长上限
dalvik.vm.heapsize堆的划分大小
dalvik.vm.heaptargetutilization理想的堆内存利用率
dalvik.vm.heapminfree单次堆内存调整的最小值
dalvik.vm.heapmaxfree单次堆内存调整的最大值

dalvik.vm.heaptargetutilization其取值位于0与1之间。当GC进行完垃圾回收之后,Dalvik的堆内存会进行相应的调整,通常结果是当前存活的对象的大小与堆内存大小做除法,得到的值为这个选项的设置,即这里的0.75。注意,这只是一个参考值, Dalvik虚拟机也可以忽略此设置。(毕竟是理想状态)

谷歌的配置建议如下图
在这里插入图片描述
在MTK平台上,可以通过设置device路径下的/mediatek/mt6xxx/device.mk文件中进行设置

内存回收方式

内存回收操作主要针对的就是内存中的文件页(file cache)和匿名页。

关于活跃(active)还是不活跃(inactive)的判断内核会使用lru算法进行处理并进行标记,以后有机会再进行了解。
整个扫描的过程分几个循环:

  1. 首先扫描每个zone上的cgroup组;
  2. 然后再以cgroup的内存为单元进行page链表的扫描;
  3. 内核会先扫描anon的active链表,将不频繁的放进inactive链表中,然后扫描inactive链表,将里面活跃的移回active中;
  4. 进行swap的时候,先对inactive的页进行换出;
  5. 如果是file的文件映射page页,则判断其是否为脏数据,如果是脏数据就写回,不是脏数据可以直接释放。

这样看来, 内存回收这个行为会对两种内存的使用进行回收:

  • 一种是anon的匿名页内存,主要回收手段是swap;
  • 另一种是file-backed的文件映射页,主要的释放手段是写回和清空。

因为针对filebased的内存,没必要进行交换,其数据原本就在硬盘上,回收这部分内存只要在有脏数据时写回,并清空内存就可以了,以后有需要再从对应的文件读回来。

swappiness是啥捏

swappiness 代表swap out的激进性,值越大越倾向swap anon page to zram 来进行回收内存(swap分区使用率越高), 值越小越倾向回收file page

swappiness有vm.swappiness 和memory.swappiness的区别。如果没有定义 CONFIG_MEMCG(该宏默认打开),则使用前者,如有定义,则二者一同使用。

vm.swappiness决定的是全局页回收。该值为0时,只有在进行全局回收,并且file page+free page<=total_high_wmark时才进行匿名回收。

而memory.swappiness 该参数值则对当前cgroup生效,其功能和vm.swappiness一样,唯一的区别是,如果memory.swappiness设置成0,就算系统配置的有交换空间,当前cgroup也不会使用交换空间。

很多人应该都知道 /proc/sys/vm/swappiness 这个文件,是个可以用来调整跟swap相关的参数。这个文件的默认值是60。可以的取值范围有两种,一种是0-100,还有一种是0-200。
要在kernel-x.xx/kernel/sysctl.c文件下查看

 {
          .procname     = "swappiness",
          .data         = &vm_swappiness,
          .maxlen              = sizeof(vm_swappiness),
          .mode         = 0644,
          .proc_handler = proc_dointvec_minmax,
          .extra1              = &zero,
      #ifndef CONFIG_MTK_GMO_RAM_OPTIMIZE
          .extra2              = &one_hundred,
      #else
          .extra2              = &two_hundred,
      #endif
   },

如果设置为one_handred,则最大值为100,在这个文件里面设置的最大值超过了100则视为无效,在低端机小内存项目上,swappiness值尽量设高,大内存项目上则保持默认。
0-200的设置也是同理,设置的数值越大越会去使用swap交换区内存,数值越小越会偏向于使用物理内存。

kswapd是什么

kswapd进程是一个内核进程,它十分的重要,主要负责在内存不足时清理内存空间,在初始化时候会为系统创建一个名为kswap%d的内核进程,然后创建一个死循环的主函数。当系统内存紧张时,这时内存分配函数会调wakeup_kswapd()来唤醒kswapd内核线程,此时kswapd内核线程在kswapd_try_to_sleep函数中被唤醒,然后调用balance_pgdat()函数来回收页面。

kswapd的回收过程

  1. 既然kswapd要进行内存回收,那么我们首先明确一下在kswapd回收内存的时候有一个扫描控制结构体:明确需要回收多少内存、回收哪些内存以及回收时的操作权限等。
  2. 我们回到kswapd周期检查和直接内存回收的两种内存回收机制。直接内存回收比较好理解,当申请的内存大于剩余内存的时候,就会触发直接回收。

那么kswapd进程在周期检查的时候触发回收的条件是什么呢?

还是从设计角度来看,kswapd进程要周期对内存进行检测,达到一定阈值的时候开始进行内存回收。

这个所谓的阈值可以理解为内存目前的使用压力,就是说,虽然我们还有剩余内存,但是当剩余内存比较小的时候,就是内存压力较大的时候,就应该开始试图回收些内存了,这样才能保证系统尽可能的有足够的内存给突发的内存申请所使用。

各位如果对kswapd的初始化以及内存回收的具体流程感兴趣,可以参考文章Linux内存回收(一),讲的比较详细。

发生原因

发生kswapd0占用过高的原因,其实就是物理内存不足,通过设置的swappiness进行比例调整,将内存数据压缩放入swap分区或者清空释放,内存越小的机器越需要swap分区内存,因为需要一定量的内存空间去运行某些应用,考虑得更多的在于功能性的问题;而内存空间越大的手机则不需要那么多的swap分区空间了,因为大内存往往伴随的是高端项目,更在乎的应该偏向于用户体验,功能性的问题不会由于内存不足导致。而swap分区相对于物理内存,速度仅有其5%左右,在乎响应速度的话这还是很不划算的。

发生时间

在Linux内存达到一定的阈值了将会开始使用kswapd0进行内存回收,那么怎样去判断阈值呢?
在内核中,会设置一个水位标记,分为3个水平:high、low、min,他们分别代表的是

  • 当前可用内存在high以上:代表内存充足,处于较高水平,压力不大;
  • 内存水平在high-low中间:表示目前剩余内存存在一定压力;
  • 内存水平在low-min之间:表示内存开始有较大使用压力,剩余内存不多了
  • min:min是最小的内存标记水位,当内存达到这个水平,就说明目前内存存在很大压力;
  • 小于min:这一部分一般是Linux分配给特定情况进行使用的,一般不会分配。

一般kswapd0会在内存水平低于low时候进行使用回收,直到内存水平达到high时候停止。如果内存消耗导致剩余内存达到了或超过了[min]时,就会触发直接回收(direct reclaim)。

注意,这个watermark水位是系统计算出来的,但是更多的也与系统设置有关。

总结

内存回收也不是越多越好,预留更多的内存换来的代价可能是更多的cache清除或者压缩更多的数据至swap分区,也会明显对用户体验造成影响。一切的调整既要保证性能,也要应付用户或者系统申请的页面分配请求,达到一个平衡的情况。

以上这些只学习了swap、swappiness以及kswapd的基础概念,真正遇见了实际问题还是要结合代码去解决,学习概念只是为了自己在碰到问题时能有基础的概念,了解往哪个方向进行分析而不会一头雾水。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐