内存性能分析篇之回炉再造

Posted on 2020-04-25 23:06  hrers  阅读(844)  评论(0编辑  收藏  举报

======================内存性能分析篇之回炉再造====================
内存方面的概念:
1.只有内核才能直接访问物理内存。
2.程序需要访问内存,需要通过Linux内核给每个进程提供的独立的虚拟地址空间。地址空间是连续的,这样进程就可以方便的访问虚拟内存了。
3.虚拟地址空间的内部分为内核空间和用户空间两部分,不同字长(也就是单个CPU指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。比如常见的有32位和64位
4. 32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间。而 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。
5.进程内存管理:
每个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大得多。所以,并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
6.内存映射:其实就是将虚拟内存地址段映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。页表实际存储在CPU的内存管理单元MMU中。这样,正常情况下,处理器就可以直接通过硬件找到要访问的内存。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后返回用户空间。恢复进程的运行。
7.TLB其实就是MMU中页表的高速缓存。由于进程的虚拟地址空间是独立的,而TLB的访问速度又比MMU快得多,所以,通过减少进程的上下文切换,减少TLB的刷新次数,就可以提高TLB的缓存使用率,进而提高CPU的内存访问性能。
8.多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数。作用就是减少单一内存页过大的情况,而用多级页表来替代。
9.虚拟空间分布:内核空间+用户空间
用户空间包括:
1. 只读段,包括代码和常量等。
2. 数据段,包括全局变量等。
3. 堆,包括动态分配的内存,从低地址开始向上增长。
4. 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
5. 栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。
10.内存的分配与回收
1.malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即 brk()和 mmap()。
2.对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。 3.大块内存(大于128K),则直接使用内存映射mmap()来分配,也就是在文件映射段找一块空闲内存分配出去。
4.malloc 通过 brk() 分配的内存,在释放时并不立即归还系统,而是缓存起来重复利用。在内核空间,Linux 则通过 slab 分配器来管理小内存。你可以把 slab 看成构建在伙伴系统上的一个缓存,主要作用就是分配并释放内核中的小对象。
5.应用程序用完内存后,还需要调用 free() 或 unmap() ,来释放这些不用的内存。
6.系统内存回收机制:
回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;
回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;
杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。
7.oom
一个进程消耗的内存越大,oom_score 就越大;
一个进程运行占用的 CPU 越多,oom_score 就越小。
oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
11.内存查看命令
free -h top ps
12.注意点
第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。
第二,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的SHR直接相加得出结果。
13.内存的buffer和cache
free命令数据来源:
Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。
Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与SReclaimable 之和。

Buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。

Cached是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。

SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。

清理缓存:echo 3 > /proc/sys/vm/drop_caches

Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。
Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
自我理解:buffer针对于磁盘数据,cache针对于文件系统缓存数据。一个队磁盘进程读缓存写缓冲,一个队文件系统来说是读缓存写缓冲。

/proc/< pid >/smaps #理解:以进程的资源使用,来统计内存的使用情况

《=============================================================================》
系统缓存优化程序的运行效率:
命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好
查看系统缓存命中情况的工具:
cachestat 提供了整个操作系统缓存的读写命中情况。
cachetop 提供了每个进程的缓存命中情况。
这两个工具都是bcc软件包的一部分,它们基于Linux内核的eBPF(extendedBerkeleyPacketFilters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。

安装cachestat和cachetop命令。首先安装bcc软件包
Ubuntu:
1.安装bcc软件包
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.l
sudo apt-get update
sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r)
2.bcc软件包没有配置系统的PATH路径,所以需要手动创建
export PATH=$PATH:/usr/share/bcc/tools
===============================================================================================
未测试
centos:
1.查看内核版本
yum update -y
cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
2.安装elrepo内核
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
3.卸载旧的工具包,只保留内核
先卸载旧的 kernel-headers,kernel-tools, kernel-tools-libs,只保留内核,然后
yum --enablerepo=elrepo-kernel install kernel-ml kernel-ml-devel.x86_64
4.继续下载
yum --enablerepo=elrepo-kernel install kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs
5.设置内核启动菜单
# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
0 : CentOS Linux (4.9.0-1.el7.elrepo.x86_64) 7 (Core)
1 : CentOS Linux (3.10.0-514.2.2.el7.x86_64) 7 (Core)
2 : CentOS Linux (3.10.0-327.36.3.el7.x86_64) 7 (Core)
3 : CentOS Linux (3.10.0-327.el7.x86_64) 7 (Core)
4 : CentOS Linux (0-rescue-d67c340e997845be9d763958bab035d6) 7 (Core)
# 查看当前设置
# grub2-editenv list
saved_entry=0
# grub2-set-default 0
# reboot
6.安装gcc
yum install gcc-c++
wget https://cmake.org/files/v3.7/cmake-3.7.0.tar.gz
tar xf cmake-3.7.0.tar.gz
cd cmake-3.7.0
./configure
make -j $THREADS
sudo make install
cd ..
7.安装bison
curl -OL https://ftp.gnu.org/gnu/bison/bison-3.0.tar.xz
tar -xf bison-3.0.tar.xz
cd bison-3.0
./configure
make -j $THREADS
sudo make install
cd ..
8.Build CLANG
curl -LO http://releases.llvm.org/3.9.1/cfe-3.9.1.src.tar.xz
curl -LO http://releases.llvm.org/3.9.1/llvm-3.9.1.src.tar.xz
tar -xf cfe-3.9.1.src.tar.xz
tar -xf llvm-3.9.1.src.tar.xz
mkdir clang-build
mkdir llvm-build

cd llvm-build
cmake -G "Unix Makefiles" -DLLVM_TARGETS_TO_BUILD="BPF;X86" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr ../llvm-3.9.1.src
make -j $THREADS
sudo make install

cd ../clang-build
cmake -G "Unix Makefiles" -DLLVM_TARGETS_TO_BUILD="BPF;X86" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr ../cfe-3.9.1.src
make -j $THREADS
sudo make install
cd ..
9.下载bcc依赖
sudo yum install -y elfutils-libelf-devel flex
10.下载bcc
git clone https://github.com/iovisor/bcc.git

export CFLAGS=-I${HOME}/build/linux-4.9.71/usr/include
mkdir bcc-build
cd bcc-build
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr ../bcc
make -j $THREADS
sudo make install
========================================================================================
不可用
centos安装bcc:
第一步,升级内核。你可以运行下面的命令来操作:
# 升级系统
yum update -y
# 安装 ELRepo
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
# 安装新内核
yum remove -y kernel-headers kernel-tools kernel-tools-libs
yum --enablerepo="elrepo-kernel" install -y kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs kernel-ml-tools-libs-devel
# 更新 Grub 后重启
grub2-mkconfig -o /boot/grub2/grub.cfg
grub2-set-default 0
reboot
# 重启后确认内核版本已升级为 4.20.0-1.el7.elrepo.x86_64
uname -r

第二步,安装 bcc-tools:
# 安装 bcc-tools
yum install -y bcc-tools
# 配置 PATH 路径
export PATH=$PATH:/usr/share/bcc/tools
=========================================================================================
未成功
pcstat 这个工具,来查看文件在内存中的缓存大小以及缓存比例
安装:
安装go语言环境:
1.下载安装包
wget https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz
2.校验
官网校验码:aea86e3c73495f205929cfebba0d63f1382c8ac59be081b6351681415f4063cf
sha256sum go1.12.5.linux-amd64.tar.gz
3.解压
sudo tar -C /usr/local -xzf go1.12.5.linux-amd64.tar.gz
4.加入环境变量
export PATH=$PATH:/usr/local/go/bin
安装pcstat
export GOPATH=~/go
export PATH=~/go/bin:$PATH
go get golang.org/x/sys/unix
go get github.com/tobert/pcstat/pcstat
=======================================================================================
工具没有安装成功:
分析思路总结:
1.cachestat 确认系统整体缓存命中情况
2.cachetop 确认进程的缓存命中情况
3.通过strace命令定位到具体的进程代码,对代码缓存分析

=======================================================================================
内存的分配回收相关概念:
1.用户空间内存包含多个不同的内存段,比如只读段、数据段、堆、栈以及文件映射等。这些内存段正是应用程序使用内存的基本方式。
2.程序中定义的变量,会从内存栈中分配内存。栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统回收,所以不会产生内存泄漏的问题。
3.在事先不知道数据大小的变量,所以就要用到标准函数库malloc()_在程序中动态分配内存。这时候,系统就会从内存空间的堆中分配内存。
4.堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而需要应用程序明确调用库函数free()来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。
5.只读段,包括包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。
6.数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
7.内存映射,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致跟堆类似的问题。
8.虽然,系统最终可以通过OOM(out of memory)机制杀死进程,但进程在OOM前可能已经引发一连串的反应,导致严重的性能问题

内存溢出的判断处理逻辑:
1.通过vmstat命令观看内存的动态变化情况
*一种不太友好的判断方式:通过top和ps观察内存使用情况,最后再通过pmap查看进程的内存分布。
2.以下通过memleak命令来检测内存泄漏,memleak可以跟踪系统或指定进程的内存分配、释放请求。然后定期输出一个未释放内存和相应调用栈的汇总情况。(默认5秒)
3.memleak -a -p $(pidof 命令名) #-a表示显示每个内存分配请求的大小和地址,-p表示指定进程的pid号,pidof命令可以输出命令的pid号。
4.memleak安装见centos7安装bcc-tools工具文档
5.通过memleak命令定位到哪一个函数分配的内存没有释放,然后查看函数逻辑定位具体问题

=========================================================================================
###swap分区上升原因:

一些引导概念:
1.脏页写入磁盘的两种方式
通过系统调用fsync将脏页同步到磁盘中;
由内核线程pdflush负责这些脏数据的刷新。
2.除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取。
3.Linux的Swap机制。Swap把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。

swap原理:
1.swap的换出换入两个过程:
换出,就是把进程短暂不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
换入,则是在进程再次访问这些内存的时候,把它们从磁盘中读取出来。
2.swap的两种使用情况:
***使内存不足时,有些应用程序也并不想被OOM杀死,而是希望能缓一段时间,等待人工介入,或者等系统自动释放其他进程的内存,再分配给它。
***我们常见的笔记本电脑的休眠和快速开机的功能,也基于 Swap 。休眠时,把系统的内存存入磁盘,这样等到再次开机时,只要从磁盘中加载内存就可以。这样就省去了很多应用程序的初始化过程,加快了开机速度。
3.内存的回收机制:
***在有新的大块内存分配请求时,但剩余内存不足。这个时候系统就需要回收一部分内存(比如缓存的内容),进而尽可能满足新内存需求。这个过程被称为直接内存回收。
***系统有一个专门的内核线程用来定期回收内存,也就是kswapd0.为了衡量内存的使用情况,kswapd0定义三个内存阈值:页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high).剩余内存则使用pages_free表示

4.内存的坐落位置:
剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。
剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。
剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。
剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。
5.内存最小页阈值的设置和页高、页低阈值的间接设置
通过内核选项 /proc/sys/vm/min_free_kbytes 来间接设置。min_free_kbytes 设置了页最小阈值,而其他两个阈值,都是根据页最小阈值计算生成的
1 pages_low = pages_min*5/4
2 pages_high = pages_min*3/2
6.在 NUMA 架构下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间。
7.同一个Node内部的内存空间,实际上又可以进一步分为不同的内存域(Zone),比如直接内存访问区(DMA)、普通内存区(NORMAL)、伪内存区(MOVABLE)等
8.以通过 numactl 命令,来查看处理器在 Node 的分布情况,以及每个 Node 的内存使用情况。
numactl --hardware
9.前面提到的三个内存阈值(页最小阈值、页低阈值和页高阈值),都可以通过内存域在 proc 文件系统中的接口 /proc/zoneinfo 来查看。cat /proc/zoneinfo

***pages 处的 min、low、high,就是上面提到的三个内存阈值,而 free 是剩余内存页数,它跟后面的 nr_free_pages 相同。
***nr_zone_active_anon 和 nr_zone_inactive_anon,分别是活跃和非活跃的匿名页数。
***nr_zone_active_file 和 nr_zone_inactive_file,分别是活跃和非活跃的文件页数。

10.可以通过 /proc/sys/vm/zone_reclaim_mode 来调整内存回收机制
默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。
1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用 Swap方式回收内存。
11.文件页和匿名页的回收
对文件页的回收,当然就是直接回收缓存,或者把脏页写回磁盘后再回收。
而对匿名页的回收,其实就是通过 Swap 机制,把它们写入磁盘后再释放内存。
12.Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 回收内存的积极程度。
swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。
注:虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。

总结:
memleak命令查看命令内存分布情况
/proc/sys/vm/min_free_kbytes #调整最小页最小阈值
/proc/sys/vm/swappiness #调整swap回收内存的权重值
/proc/sys/vm/zone_reclaim_mode #调整内存回收机制
cat /proc/zoneinfo #查看页阈值指标情况

通过swap文件的方式配置swap分区:
1 # 创建 Swap 文件
2 $ fallocate -l 8G /mnt/swapfile
3 # 修改权限只有根用户可以访问
4 $ chmod 600 /mnt/swapfile
5 # 配置 Swap 文件
6 $ mkswap /mnt/swapfile
7 # 开启 Swap
8 $ swapon /mnt/swapfile

sar命令查看swap分区的使用情况
sar -r -S 1 #-r表示显示内存使用情况,-S表示显示swap使用情况
kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分比。
kbactive,表示活跃内存,也就是最近使用过的内存,一般不会被系统回收。
kbinact,表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。

watch -d grep -A 15 'Normal' /proc/zoneinfo #表示查看zoneinfo文件匹配内容之后的15行内容,watch命令动态查看

进程 Swap 换出的虚拟内存大小,它保存在/proc/pid/status 中的 VmSwap 中
# 按 VmSwap 使用量对进程排序,输出进程名称、进程 ID 以及 SWAP 用量
for i in /proc/*/status;do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{print " ",$1,$2,$3}' $i;done

swapoff -a && swapon -a关闭 Swap 后再重新打开,也是一种常用的 Swap 空间清理方法

总结:根本就是从/proc/*status文件中找出所有占用swap多的进程。辅助措施为以上的总结
注:smem --sort swap命令可以直接将进程按照swap使用量排序显示

========================================================================================
系统内存类型说明:
**已用内存和剩余内存很容易理解,就是已经使用和还未使用的内存。
**共享内存是通过 tmpfs 实现的,所以它的大小也就是 tmpfs 使用的内存大小。tmpfs 其实也是一种特殊的缓存。
**可用内存是新进程可以使用的最大内存,它包括剩余内存和可回收缓存。
**缓存包括两部分,一部分是磁盘读取文件的页缓存,用来缓存从磁盘读取的数据,可以加快以后再次访问的速度。另一部分,则是 Slab 分配器中的可回收内存。
**缓冲区是对原始磁盘块的临时存储,用来缓存将要写入磁盘的数据。这样,内核就可以把分散的写集中起来,统一优化磁盘写入。

进程内存情况说明:
**虚拟内存,包括了进程代码段、数据段、共享内存、已经申请的堆内存和已经换出的内存等。这里要注意,已经申请的内存,即使还没有分配物理内存,也算作虚拟内存。
**常驻内存是进程实际使用的物理内存,不过,它不包括 Swap 和共享内存。
**共享内存,既包括与其他进程共同使用的真实的共享内存,还包括了加载的动态链接库以及程序的代码段等。
**Swap 内存,是指通过 Swap 换出到磁盘的内存。

缺页异常:
在内存分配的原理中,我曾经讲到过,系统调用内存分配请求后,并不会立刻为其分配物理内存,而是在请求首次访问时,通过缺页异常来分配。缺页异常又分为下面两种场景。
**可以直接从物理内存中分配时,被称为次缺页异常
**需要磁盘 I/O 介入(比如 Swap)时,被称为主缺页异常

swap分区说明:
已用空间和剩余空间很好理解,就是字面上的意思,已经使用和没有使用的内存空间。
换入和换出速度,则表示每秒钟换入和换出内存的大小。

总结:之前分析的思路
第一个例子,当你通过free,发现大部分内存都被缓存占用后,可以使用vmstat或者sar察一下缓存的变化趋势,确认缓存的使用是否还在继续增大。如果继续增大,则说明导致缓存升高的进程还在运行,那你就能用缓存 / 缓冲区分析工具(比如 cachetop、slabtop 等),分析这些缓存到底被哪里占用。
第二个例子,当你 free 一下,发现系统可用内存不足时,首先要确认内存是否被缓存 / 缓冲区占用。排除缓存 / 缓冲区后,你可以继续用pidstat或者top,定位占用内存最多的进程。找出进程后,再通过进程内存空间工具(比如pmap),分析进程地址空间中内存的使用情况就可以了。
第三个例子,当你通过vmstat或者sar发现内存在不断增长后,可以分析中是否存在内存泄漏的问题。比如你可以使用内存配分析工具 memleak ,检查是否存在内存泄漏。如果存在内存泄漏问题,memleak 会为你输出内存泄漏的进程以及调用堆栈。

内存性能的分析思路:
1. 先用 free 和 top,查看系统整体的内存使用情况。
2. 再用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。
3. 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。

内存优化思路:
1. 最好禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时 Swap的使用倾向。
2. 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。
3. 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。
4. 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。
5. 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。这样,可以保证即使内存紧张,核心应用也不会被 OOM 杀死。

=====================================================================================
统计进程的实际内存:
# 使用 grep 查找 Pss 指标后,再用 awk 计算累加值
grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {printf "%d kB\n", total }'

缓存回收和 Swap 回收,实际上都是基于 LRU 算法,也就是优先回收不常访问的内存。LRU 回收算法,实际上维护着 active 和 inactive 两个双向链表,其中:
active 记录活跃的内存页;
inactive 记录非活跃的内存页。
越接近链表尾部,就表示内存页越不常访问。这样,在回收内存时,系统就可以根据活跃程度,优先回收不活跃的内存。
活跃和非活跃的内存页,按照类型的不同,又分别分为文件页和匿名页,对应着缓存回收和Swap 回收。当然,你可以从 /proc/meminfo 中,查询它们的大小,比如:
cat /proc/meminfo | grep -i active | sort

dmesg | grep -i "Out of memory" #查看OOM信息