文章目录

  • 前言
  • 内存的分配机制
  • 回收可回收内存的类型
  • 如何在保证系统性能前提下回收内存
    • 可回收类型角度: 调整文件页回收倾向
    • 回收的方式角度: 尽早触发kswapd内核线程
    • 从计算机CPU架构角度: 采用NUMA
  • 如何保护进程不被OOM杀掉
  • 总结

前言

当我们向操作系统申请内存时候,是否有想过一个问题:如果当前系统物理内存不足以支撑我们所需要的空间容量,操作系统会进行哪些的相关处理来保证满足我们的要求?

答案是涉及接下来我们要讲解的几个方面

内存的分配机制, 回收可回收内存, 如何在保证性能的其拉提下满足要求,以及如何保证不被OOM机制处死

内存的分配机制

现代操作系统大部分都是使用虚拟内存的页或者段页式内存管理机制,当我们使用malloc等内存申请函数调用时候,所分配到的空间实际是虚拟内存,并不会分配到物理内存,当程序下次进行读取该内存时,CPU就会去访问该虚拟内存,然后发现其并没有和物理内存进行映射产生关联,CPU就会发生缺页中断,然后进程从用户态转移到内核态,交给操作系统执行PAGE_Fault(缺页中断处理函数);

此时PAGE_FAULT会检查当前空余物理内存是否足够,若足够,则进行建立和物理内存的映射关系,若不足,则通过操作系统提供的3种常见内存回收机制进行回收内存;

3种常见内存回收机制:

  • 后台 回收机制(异步,不阻塞)

    唤醒kswapd内核线程来回收内存,此过程异步进行,不会阻塞进程的执行.

  • 直接 回收机制(同步,阻塞)

    当后台异步回收机制跟不上进程内存申请读写的速度,就开始直接回收机制,此过程同步进行,会阻塞进程的执行.

  • OOM(out of memory)

    当直接回收机制都无法满足内存申请要求,则执行OOM Killer 机制,它会根据算法选择一个占用物理内存较高的进程,然后将其杀死以释放内存资源,如果依然不足,OOM Killer 将会继续杀死占用物理内存更高的进程,直到释放足够的内存为止.

回收可回收内存的类型

通过上面的内存分配流程图,我们可以知道当物理内存不足以支撑需求的时候,会进行执行相关的内存回收机制,但是到底回收的是哪部分内存呢?

主要分为两类

  • 文件页(file-backed pages) 内存:进程虚拟地址空间中的代码段,以及映射的文件一般称为文件页

    大部分文件页,都可以直接释放内存,当以后有需要再从磁盘读取.而那些被修改过且还未写入磁盘的数据(脏页),就得先写入磁盘才能进行内存释放。所以,回收干净页的方式是直接释放内存,回收脏页的方式是先写回磁盘后再释放内存

  • 匿名页(anonymous pages) 内存:进程虚拟地址空间的堆,栈,数据段一般称为匿名页

    这部分内存一般是应用程序临时产生的,所以也不能直接释放内存,它们回收的方式是通过 Linux 的 Swap 机制,Swap 会把不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了

更多关于文件页和匿名页介绍请看这里

文件页和匿名页的回收都是基于 LRU(Least Rencently Used) 算法,也就是回收最近最少使用的内存。该算法维护着 active 和 inactive 两个双向链表:

  • active_list 活跃内存页链表,存放最近被访问过(活跃)的内存页;
  • inactive_list 不活跃内存页链表,存放最近很少被访问(非活跃)的内存页;

越接近链表尾部,就表示内存页越不常访问。因此,在回收内存时,系统就可以根据活跃程度,优先回收不活跃的内存。

其中在活跃页和非活跃页内部,又分对文件页(file)和匿名页(anonymous)进行了分类,可以使用linux shell命令cat /proc/meminfo | grep -i active | sort进行查看:

如何在保证系统性能前提下回收内存

从上面两个小节可以看出,以回收方式的角度看待,直接回收会阻塞进程;以可回收内存的类型角度看,脏页和匿名页回收会引起磁盘IO;

也就是说无论怎么样,只要回收内存,就很容易发生IO事件,如果频繁,就会引起系统整体性能降低,即宏观感受到的就是系统卡顿;

那么回收内存时候,怎么可以保证性能呢?,一般通过以下三个角度看待:

可回收类型角度: 调整文件页回收倾向

文件页主要分为干净页和脏页,其中干净页的回收不需要发生磁盘IO,因此文件页的效率相对匿名页来说,回收效率更高,我们可以提高系统回收文件页的概率(或者说倾向)高于匿名页;

linux系统给我们提供了一个选项来进行调整

/proc/sys/vm/swappiness

swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。

所以为了效率,一般可以将swappiness的值设为0,这样当系统回收内存时候,就更倾向于去回收文件页(注意哦,说的是倾向,不是说就一定回收文件页)

回收的方式角度: 尽早触发kswapd内核线程

后台回收机制通过唤醒kswapd线程进行异步回收内存,并不会影响系统性能,因此可以通过尽早触发kswapd的形式

但是触发kswapd的条件是什么呢?

内核定义了三个内存阈值(watermark,也称为水位线),用来衡量当前剩余内存是否充裕或者紧张,分别是:

  • 页高阈值(pages_high);

  • 页低阈值(pages_low);

  • 页最小阈值(pages_min);

kswapd 会定期扫描内存的使用情况,根据剩余内存(pages_free)的情况来进行内存回收的工作。

  • 图中绿色部分:如果剩余内存(pages_free)大于 页高阈值(pages_high),说明剩余内存是充足的;
  • 图中蓝色部分:如果剩余内存(pages_free)在页高阈值(pages_high)和页低阈值(pages_low)之间,说明内存有一定压力,但还可以满足应用程序申请内存的请求;
  • 图中橙色部分:如果剩余内存(pages_free)在页低阈值(pages_low)和页最小阈值(pages_min)之间,说明内存压力比较大,剩余内存不多了。这时 kswapd0线程就会执行内存回收,直到剩余内存大于高阈值(pages_high)为止。虽然会触发内存回收,但是不会阻塞应用程序,因为两者关系是异步的。
  • 图中红色部分:如果剩余内存(pages_free)小于页最小阈值(pages_min),说明用户可用内存都耗尽了,此时就会触发直接内存回收,这时应用程序就会被阻塞,因为两者关系是同步的。

也就是说,触发kswapd线程的条件就是pages_min < 剩余内存 < pages_low

知道了这个条件后,我们就可以设置pages_min,pages_low和pages_heigh的值,进行调整触发kswapd的条件难易了

系统给我们提供了选项

/proc/sys/vm/min_free_kbytes

而它们三者之间的关系是:

pages_min = min_free_kbytes
pages_low = pages_min*5/4
pages_high = pages_min*3/2

当设置好我们想要的参数以后,可以通过sar -B 1命令查看系统直接回收和后台回收的指标变化了

  • pgscank/s : kswapd(后台回收线程) 每秒扫描的 page 个数。
  • pgscand/s: 应用程序在内存申请过程中每秒直接扫描的 page 个数。
  • pgsteal/s: 扫描的 page 中每秒被回收的个数(pgscank+pgscand)。

倘若在该指标变化中,发现pgscand/s的值很大,就可能是系统采用了直接回收内存的方式,因此我们可以继续采用调整min_free_bytes的值大小方式进行尽早触发kswapd内核线程

从计算机CPU架构角度: 采用NUMA

什么是UMA结构?

每个 CPU 地位平等,它们共享相同的物理资源,包括总线、内存、IO、操作系统等,每个 CPU 访问内存所用时间都是相同的,因此,这种系统被称为一致存储访问结构(UMA,Uniform Memory Access)。

而随着 CPU 处理器核数的增多,多个 CPU 都通过一个总线访问内存,这样总线的带宽压力会越来越大,同时每个 CPU 可用带宽会减少,为了解决此问题,便提出了NUMA结构,即非一致存储访问结构,NUMA结构将每个 CPU 进行了分组,每一组 CPU 用 Node 来表示,一个 Node 可能包含多个 CPU ,每个 Node 有自己独立的资源,包括内存、IO 等,每个 Node 之间可以通过互联模块总线(QPI)进行通信,所以,也就意味着每个 Node 上的 CPU 都可以访问到整个系统中的所有内存。但是,访问远端 Node 的内存比访问本地内存要耗时很多。

在 NUMA 架构下,当某个 Node 内存不足时,系统可以从其他 Node 寻找空闲内存,也可以从本地内存中回收内存。

具体选哪种模式,可以通过 /proc/sys/vm/zone_reclaim_mode 来控制。它支持以下几个选项:

  • 0 (默认值):在回收本地内存之前,在其他 Node 寻找空闲内存;
  • 1:只回收本地内存;
  • 2:只回收本地内存,在本地回收内存时,可以将文件页中的脏页写回硬盘,以回收内存。
  • 4:只回收本地内存,在本地回收内存时,可以用 swap 方式回收内存。

在使用 NUMA 架构的服务器,如果系统出现还有一半内存的时候,却发现系统频繁触发「直接内存回收」,导致了影响了系统性能,那么大概率是因为 zone_reclaim_mode 没有设置为 0 ,导致当本地内存不足的时候,只选择回收本地内存的方式,而不去使用其他 Node 的空闲内存。

虽然说访问远端 Node 的内存比访问本地内存要耗时很多,但是相比内存回收的危害而言,访问远端 Node 的内存带来的性能影响还是比较小的。因此,zone_reclaim_mode 一般建议设置为 0

如何保护进程不被OOM杀掉

在系统空闲内存不足的情况,进程申请了一个很大的内存,如果直接内存回收都无法回收出足够大的空闲内存,那么就会触发 OOM 机制,内核就会根据算法选择一个进程杀掉。

Linux 到底是根据什么标准来选择被杀的进程呢?这就要提到一个在 Linux 内核里有一个 oom_badness() 函数,它会把系统中可以被杀掉的进程扫描一遍,并对每个进程打分,得分最高的进程就会被首先杀掉。

进程得分的结果受下面这两个方面影响:

  • 第一,进程已经使用的物理内存页面数。
  • 第二,每个进程的 OOM 校准值 oom_score_adj。它是可以通过 /proc/[pid]/oom_score_adj 来配置的。我们可以在设置 -1000 到 1000 之间的任意一个数值,调整进程被 OOM Kill 的几率。

函数 oom_badness() 里的最终计算方法是这样的:

// points 代表打分的结果
// process_pages 代表进程已经使用的物理内存页面数
// oom_score_adj 代表 OOM 校准值
// totalpages 代表系统总的可用页面数
points = process_pages + oom_score_adj*totalpages/1000

用「系统总的可用页面数」乘以 「OOM 校准值 oom_score_adj」再除以 1000,最后再加上进程已经使用的物理页面数,计算出来的值越大,那么这个进程被 OOM Kill 的几率也就越大

每个进程的 oom_score_adj 默认值都为 0,所以最终得分跟进程自身消耗的内存有关,消耗的内存越大越容易被杀掉。我们可以通过调整 oom_score_adj 的数值,来改成进程的得分结果:

  • 如果你不想某个进程被首先杀掉,那你可以调整该进程的 oom_score_adj,从而改变这个进程的得分结果,降低该进程被 OOM 杀死的概率。
  • 如果你想某个进程无论如何都不能被杀掉,那你可以将 oom_score_adj 配置为 -1000。

我们最好将一些很重要的系统服务的 oom_score_adj 配置为 -1000,比如 sshd,因为这些系统服务一旦被杀掉,我们就很难再登陆进系统了。

但是,不建议将我们自己的业务程序的 oom_score_adj 设置为 -1000,因为业务程序一旦发生了内存泄漏,而它又不能被杀掉,这就会导致随着它的内存开销变大,OOM killer 不停地被唤醒,从而把其他进程一个个给杀掉。

总结

内核在给应用程序分配物理内存的时候,如果空闲物理内存不够,那么就会进行内存回收的工作,主要有两种方式:

  • 后台内存回收:异步
  • 直接内存回收:同步

而可以回收的类型也有两种:

  • 文件页
  • 匿名页

针对回收内存导致的性能影响,常见的解决方式。

  • 调整文件页和匿名页的回收倾向: 设置 /proc/sys/vm/swappiness 的值
  • 调整 kswapd 内核线程异步回收内存的时机: 设置 /proc/sys/vm/min_free_kbytes的值
  • 调整 NUMA 架构下内存回收策略: 设置 /proc/sys/vm/zone_reclaim_mod的值

当这三个性能都不能完成目标内存的申请时,就会采用OOM机制,杀掉一些根据系统评分出来的最高得分的进程,直到满足申请要求为止;

剩余内存无法满足申请时,系统会怎么做?相关推荐

  1. Java 内存管理、JVM 工作原理与 Java 运行时系统

    Java 虚拟机规范中说明:所有的对象实例(all class instances)以及数组都要在堆上分配: the heap is the runtime data area from which ...

  2. shell脚本编写监控本机内存和硬盘剩余空间,剩余内存小于 500M、根分区剩余空间小于 1000M时,发送报警邮件给 root 管理员

    监控本机内存和硬盘剩余空间,剩余内存小于 500M.根分区剩余空间小于 1000M时,发送报警邮件给 root 管理员 # 创建shell脚本文件 vim free.sh #!/bin/bash di ...

  3. THRUST:一个开源的、面向异构系统的并行编程语言:编程模型主要包括:数据并行性、任务并行性、内存管理、内存访问控制、原子操作、同步机制、错误处理机制、混合编程模型、运行时系统等

    作者:禅与计算机程序设计艺术 1.简介 https://github.com/NVIDIA/thrust 2021年8月,当代科技巨头Facebook宣布其开发了名为THRUST的高性能计算语言,可用 ...

  4. 1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈

    1.Java虚拟机内存管理 1.1.运行时数据区[Runtime Data Area] 1.1.1.线程共享区 1.1.1.1.Java堆[heap] 1.1.1.1.1.新生代.老年代.Eden区域 ...

  5. 内存资源的申请与释放(CC++)

    在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃. 所以一定要保证你的malloc和free成对出现,如果 ...

  6. C++之内存管理:申请与释放

    目录 前言 1.C/C++内存分布 1.1虚拟内存分段 1.2理解一些概念 1.2.1栈帧向下增长 1.2.2堆向上生长 1.2.3栈和堆会碰撞吗? 1.2.4关于const的说明 2.C语言中动态内 ...

  7. 软著申请时的软件说明书如何制作

    本篇文章主要讲解,软著申请时的软件说明书的使用方法 模板下载地址:https://download.csdn.net/download/hj960511/85020804 作者:任聪聪 软著申请的材料 ...

  8. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  9. sap采购申请自动转采购订单_我的SAP运维日常_0021_MM_计划订单转采购申请时绑定凭证类型...

    大家好,我是林恩~谢谢大家的喜欢和关注喔. 我的SAP运维日常专栏是跟大家分享自己运维过程中遇到的问题和解决方案哦~目前碰到的问题来自各个模块,MM\WM\PP\SD\QM\FICO,还有一些比较ge ...

最新文章

  1. 机器学习中的数据泄露是什么?构建模型中如何防止数据泄露?正确的方案是什么?如何使用pipeline防止数据泄露?
  2. html 猫链接怎么写,html超链接下划线应该加入吗?
  3. CF908G New Year and Original Order
  4. SyntaxError: expected expression, got ''
  5. python中read()、readline()、readlines()函数
  6. Eureka的服务自我保护
  7. 方舟编译器服务器端Java怎么用_方舟编译器可以运行Hello World了!
  8. 小米第三季经调整利润29亿 获美图智能手机全球授权
  9. Java入门 简易计算器
  10. 使用LayoutAnimationController为RecyclerView添加动画
  11. SQL2008R2的 遍历所有表更新统计信息 和 索引重建
  12. 数据库重建索引 计划任务
  13. 屏保延迟启用密码保护
  14. 项目管理笔记(观念)
  15. 牛逼!支付宝 App架构
  16. 运维日常操作--linux命令
  17. javascript--一个简洁的管理菜单
  18. idea 中文字体 自动变_提高工作效率,我推荐讯飞语记,瞬间语音秒变文字
  19. 社招腾讯,阿里,京东,必问知识点整理,常考知识点全在这里了!
  20. 新浪微博和腾讯微博图标

热门文章

  1. while(1);是死循环的意思。只要括号里为非零,也就是真值,它就一直循环这条句子。
  2. (华电网)通信技术基础知识
  3. 存两张《红衣坊》的海报
  4. matlab随机数的设计及应用,matlab随机数的应用
  5. matlab 随机数有效数字,MATLAB中生成随机数方法总结
  6. 运算放大器的功耗计算
  7. 求差集(C语言)两个集合的差集定义如下:集合A、B的差集,由所有属于A但不属于B的元素构成。输入两个集合A、B,每个集合中元素都是自然数。求集合A、B的差集。
  8. Oracle 11g图文下载安装教程(一步一图)
  9. wargame_bandit level0-25全攻略
  10. 概念设计.逻辑设计.物理设计.的简单说明