Linux性能调优之内存负载调优的一些笔记

简介: + 整理一些Linux内存调优的笔记,分享给小伙伴,博文没有涉及的Demo,理论方法偏多,可以用作内存调优入门 博文内容涉及: Linux内存管理的基本理论 寻找内存泄露的进程 内存交换空间调优 不同方式的内存回收 食用方式 需了解Linux基础知识 理解不足小伙伴帮忙指正

写在前面

  • 整理一些Linux内存调优的笔记,分享给小伙伴
  • 博文没有涉及的Demo,理论方法偏多,可以用作内存调优入门
  • 博文内容涉及:

    • Linux内存管理的基本理论
    • 寻找内存泄露的进程
    • 内存交换空间调优
    • 不同方式的内存回收
  • 食用方式

    • 需了解Linux基础知识
  • 理解不足小伙伴帮忙指正

原谅和忘记就意味着扔掉了我们获得的最贵经验 -------《人生的智慧》叔本华


在现代处理器中,与CPU执行代码或处理信息相比,向内存子系统保存信息或从中读取信息一般花费的时间更长

通常,在CPU执行指令或处理数据前,它会消耗相当多的空闲时间来等待从内存中取出指令和数据。处理器用不同层次的高速缓存(cache)来弥补这种缓慢的内存性能。

内存管理

内存是内核所做的比较复杂的事情之一。高效的内存管理对于系统中进程的良好性能至关重要。现代计算机系统使用分页来安全、灵活地管理系统内存。

为了提高效率,Linux将其分成内存“页”。当对内存进分配或传送时,Linux操作的单位是页,而不是单个字节。在报告一些内存统计数据时,Linux内核报告的是每秒页面的数量,该值根据其运行的架构可以发生变化。

系统上的物理RAM被分为页帧;一页帧保存一页数据。进程不直接寻址物理内存。相反,每个进程都有一个虚拟地址空间。当进程被分配内存时,页帧的物理地址被映射到进程的一个虚拟地址。从进程的角度来看,它有一个私有的内存空间,它只能看到映射到它的一个虚拟地址的物理页帧。

进程虚拟地址空间(页面)的大小取决于处理器架构。在32位i386系统上,一个进程的虚拟地址空间可以容纳2^32 个字节(4gib)的内存;在64位x86-64系统上,地址空间的大小是2^64 字节(16 EiB)。然而,单个进程通常不会使用它的整个地址空间;它的大部分是未分配的,也没有映射到任何实际的物理内存。但是虚拟地址空间的大小确实限制了进程可以拥有的最大内存

一些内存涉及的名词解释

交换分区(物理内存不足)

所有系统RAM芯片的物理内存容量都是固定的。即使应用程序需要的内存容量大于可用的物理内存,Linux内核仍然允许这些程序运行。Linux内核使用硬盘作为临时存储器,这个硬盘空间被称为交换分区(swap space)

尽管交换是让进程运行的极好的方法,但它却慢的要命。与使用物理内存相比,应用程序使用交换的速度可以慢到一千倍。如果系统性能不佳,确定系统使用了多少交换通常是有用的。

缓冲区(buffer)和缓存(cache)(物理内存太多)

缓存(cache)

相反,如果你的系统物理内存容量超过了应用程序的需求,Linux就会在物理内存中缓存近期使用过的文件,这样,后续访问这些文件时就不用去访问硬盘了。

对要频繁访问硬盘的应用程序来说,这可以显著加速其速度,显然,对经常启动的应用程序而言,这是特别有用的。

应用程序首次启动时,它需要从硬盘读取;但是,如果应用程序留着缓存中,那它就需要从更快速的物理内存读取。

这个硬盘缓存不同于前面章节提到的处理器高速缓存(cache)

缓冲区(buffer)

Linux还使用了额外的存储作为缓冲区。为了进一步优化应用程序,Linux为需要被写回硬盘的数据预留了存储空间。这些预留空间被称为缓冲区。如果应用程序要将数据写回硬盘,通常需要花费较长时间,Linux让应用程序立刻继续执行,但将文件数据保存到内存缓冲区。在之后的某个时刻,缓冲区被刷新到硬盘,而应用程序可以立即继续

高速缓存和缓冲区的使用使得系统内空闲的内存很少,默认情况下,Linux试图尽可能多的使用你的内存。这是好事。

如果Linux侦测到有空闲内存,它就会将应用程序和数据缓存到这些内存以加速未来的访问。由于访问内存的速度比访问硬盘的速度快了几个数量级,因此,这就可以显著地提升整体性能

如果系统需要缓存空间做更重要的事情,那么缓存空间将被擦除并交给系统。之后,对原来被缓存对象的访问就需要转向硬盘来满足。

活跃与非活跃内存

  • 活跃内存是指当前被进程使用的内存。
  • 不活跃内存是指已经被分配了,但暂时还未使用的内存。

这两种类型的内存没有本质上的区别。需要时,Linux找出进程最近最少使用的内存页面,并将它们从活跃列表移动到不活跃列表。当要选择把哪个内存页交换到硬盘时,内核就从不活跃内存列表中进行选择

内核的内存使用情况(分片)

除了应用程序需要分配内存外,Linux内核也会为了记账的目的消耗一定量的内存。

记账包括,比如跟踪从网络或磁盘I/O来的数据,以及跟踪哪些进程正在运行,哪些正在休眠。为了管理记账,内核有一系列缓存,包含一个或多个内存分片。每个分片为一组对象,个数可以是一个或多个。

内核消耗的内存分片数量取决于使用的是Linux内核的哪些部分,而且还可以随着机器负载类型的变化而变化。

进程内存

当进程试图访问虚拟内存中尚未分配物理页面的页面时, 即应用程序中没有加载的内存中数据,将触发页面错误

  • 如果需要分配一个新的物理内存页,这被称为次要页面故障
  • 如果内核需要从磁盘检索一页内存,则称为重大页面故障

发生重大页面错误的原因包括恢复已换出的虚拟内存页面,或将数据或代码从可执行文件映射到内存。次要页面错误会导致少量开销,但是重大页面错误会对性能产生重大影响。

要查看每个进程的次要和主要页面错误,你可以使用ps命令,询问minfltmajflt列:

┌──[root@liruilongs.github.io]-[~]
└─$ ps -o pid,cmd,comm,minflt,majflt $$
   PID CMD                         COMMAND         MINFLT MAJFLT
 12280 -bash                       bash             11203      0
┌──[root@liruilongs.github.io]-[~]
└─$ ps -o pid,cmd,comm,minflt,majflt 889
   PID CMD                         COMMAND         MINFLT MAJFLT
   889 /usr/bin/etcd --name=defaul etcd              7091     90

使用cgroups限制内存

进程可用的内存量可以用cgroup内存控制器来限制。在内存cgroup中有几个相关的文件涉及内核参数:

┌──[root@liruilongs.github.io]-[~]
└─$ ls /sys/fs/cgroup/memory/
cgroup.clone_children           memory.kmem.tcp.limit_in_bytes      memory.oom_control
cgroup.event_control            memory.kmem.tcp.max_usage_in_bytes  memory.pressure_level
cgroup.procs                    memory.kmem.tcp.usage_in_bytes      memory.soft_limit_in_bytes
cgroup.sane_behavior            memory.kmem.usage_in_bytes          memory.stat
docker                          memory.limit_in_bytes               memory.swappiness
memory.failcnt                  memory.max_usage_in_bytes           memory.usage_in_bytes
memory.force_empty              memory.memsw.failcnt                memory.use_hierarchy
memory.kmem.failcnt             memory.memsw.limit_in_bytes         notify_on_release
memory.kmem.limit_in_bytes      memory.memsw.max_usage_in_bytes     release_agent
memory.kmem.max_usage_in_bytes  memory.memsw.usage_in_bytes         system.slice
memory.kmem.slabinfo            memory.move_charge_at_immigrate     tasks
memory.kmem.tcp.failcnt         memory.numa_stat                    user.slice

看一个docker 进程的相关信息

  • memory.stat: :这个cgroup中内存和交换分区的使用情况以及内存总量的详细统计。

dockers配置了开机自动启动,从Cgroup角度看,所以他在是systemd这个slicp下面,作为一个service单元受CGroup管理

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]
└─$ cat ./memory.stat
cache 3657728
rss 144375808
rss_huge 113246208
mapped_file 0
swap 0
pgpgin 25693
pgpgout 23278
pgfault 84168
pgmajfault 0
inactive_anon 4096
active_anon 144375808
inactive_file 397312
active_file 3256320
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 3657728
total_rss 144375808
total_rss_huge 113246208
total_mapped_file 0
total_swap 0
total_pgpgin 25693
total_pgpgout 23278
total_pgfault 84168
total_pgmajfault 0
total_inactive_anon 4096
total_active_anon 144375808
total_inactive_file 397312
total_active_file 3256320
total_unevictable 0
  • memory.limit_in_bytes:在这里写入一个值,以限制这个cgroup中可以使用的用户内存(包括缓存)的数量。字节以外的单位可以通过在被设置的值后面加上k、m或g来使用。

所以我们可以通过修改这个值来限制docker的内存使用

┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]
└─$ cat ./memory.limit_in_bytes
9223372036854771712
$ echo 1G > ./memory.limit_in_bytes
  • memory.memsw.limit_in_bytes:类似于memory.limit_in_bytes,但这一次是内存+交换组合。
┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]
└─$ cat ./memory.memsw.limit_in_bytes
9223372036854771712

当然也可以在单位配置文件 [Service] 部分中添加下面的命令:

MemoryLimit=value

cgroup 中执行的进程设定其可用内存的最大值,同样,MemoryAccounting 参数必须在同一单元中启用MemoryAccounting=yes打开此单元的进程和内核内存占。接受一个布尔参数 ,这里 MemoryLimit 参数可以控制 memory.limit_in_bytes Cgroup参数实现内存限制

其他的内核参数

  • memory.failcntmemory.memsw.failcnt:这些文件报告内存的频率。
┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]
└─$ cat ./memory.failcnt
0
┌──[root@liruilongs.github.io]-[/sys/fs/cgroup/memory/system.slice/docker.service]
└─$ cat memory.memsw.failcnt
0

要查看进程虚拟地址空间是如何使用的,可以使用pmap PID命令,或者查看/proc/PID/maps/proc/PID/maps

┌──[root@liruilongs.github.io]-[~]
└─$ pmap 889 | head  -10
889:   /usr/bin/etcd --name=default --data-dir=/var/lib/etcd/default.etcd --listen-client-urls=http://192.168.26.55:2379,http://localhost:2379
000000c000000000      8K rw---   [ anon ]
000000c41ffc6000   6376K rw---   [ anon ]
000000c420600000   1216K rw---   [ anon ]
00005618c7aaa000  15784K r-x-- etcd
00005618c8c14000   8672K r---- etcd
00005618c948c000    332K rw--- etcd
00005618c94df000    168K rw---   [ anon ]
00005618ca601000    132K rw---   [ anon ]
00007ff23c000000    132K rw---   [ anon ]
┌──[root@liruilongs.github.io]-[~]
└─$ cat /proc/889/maps | head  -10
c000000000-c000002000 rw-p 00000000 00:00 0
c41ffc6000-c420600000 rw-p 00000000 00:00 0                              [stack:889]
c420600000-c420730000 rw-p 00000000 00:00 0
5618c7aaa000-5618c8a14000 r-xp 00000000 08:01 272289927                  /usr/bin/etcd
5618c8c14000-5618c948c000 r--p 00f6a000 08:01 272289927                  /usr/bin/etcd
5618c948c000-5618c94df000 rw-p 017e2000 08:01 272289927                  /usr/bin/etcd
5618c94df000-5618c9509000 rw-p 00000000 00:00 0
5618ca601000-5618ca622000 rw-p 00000000 00:00 0                          [heap]
7ff23c000000-7ff23c021000 rw-p 00000000 00:00 0
7ff23c021000-7ff240000000 ---p 00000000 00:00 0
┌──[root@liruilongs.github.io]-[~]
└─$ cat /proc/889/smaps | head  -10
c000000000-c000002000 rw-p 00000000 00:00 0
Size:                  8 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
┌──[root@liruilongs.github.io]-[~]
└─$

找到内存泄漏

有时候进程在使用完内存后不能正确地释放内存

如果进程是一个短生命周期的进程,如lsnetstat,这不是一个大问题,因为当一个进程退出时,它的所有内存都会被内核释放,

如果它是一个长时间运行的进程,问题可能会变得相当严重。

除了杀死并重新启动进程外,系统管理员在修复内存泄漏方面所能做的事情并不多。识别内存泄漏是系统管理员的职责之一。

要识别内存泄漏,可以使用通用工具,如ps,top,free,sar -rsar -R

但也有专门的工具,如valgrind工具memcheck。要在memcheck工具下运行进程,可以使用如下命令:valgrind --tool=memcheck program args-pro

要获得关于程序中哪个函数占用内存的更详细信息,你可以usc thc选项——lck -chcck=full。

┌──[root@liruilongs.github.io]-[~]
└─$ yum -y install valgrind
┌──[root@liruilongs.github.io]-[~]
└─$ rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm
准备中...                          ################################# [100%]
正在升级/安装...
   1:bigmem-7.0-1.r29766              ################################# [100%]

有两种不同类型的内存泄漏需要注意。

  • 在第一种情况下,内存泄露,程序通过malloc(一种分配内存块的函数)等系统调用请求内存,但实际上并不使用这些内存。这将导致程序的虚拟大小增加(VIRT),以及/proc/meminfo中的Committed_AS(当前在系统上分配的内存量。提交的内存是进程分配的所有内存的总和,即使它还没有被它们“使用”)行,但没有使用实际的物理内存。常驻大小(顶部的RSS)保持(几乎)不变。

对应的参数我么可以通过top命令获取,top版本不同,对应的列名有些差别

top - 11:48:07 up 14 min,  1 user,  load average: 0.07, 0.10, 0.13
Tasks: 273 total,   2 running, 271 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 32931532 total, 23319380 free,  8643596 used,   968556 buff/cache
KiB Swap: 10485756 total, 10485756 free,        0 used. 23759444 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
   889 etcd      20   0 10.559g  29332  11008 S   1.3  0.1   0:10.94 etcd
  8089 nginx     20   0   41636  12148   1588 S   1.3  0.0   0:03.35 redis-server
选项 说明
%MEM 进程使用内存量占系统物理内存的百分比
VIRT(v3.x)/SIME(v2.x) 进程虚拟内存使用总量。其中包括了应用程序分配到但未使用的全部内存
SWAP 进程使用的交换区(单位为KB)总量
RSS(v2.x)/RES(v3.x) 应用程序实际使用的物理内存总量
SHARE(v 2.x)/SHR(V 3.x) 可与其他进程共享的内存总量(单位为KB)
Mem:total,used,free 对物理内存来说,该项表示的是其总量、使用量和空闲量
swap:total,used,free 对交换分区来说,该项表示的是其总量、使用量和空闲量
buff/cache 用于缓冲区写人硬盘的数值和缓存的物理内存总量(单位为KB)
  • 在第二种情况下,内存溢出,程序实际使用它分配的内存。这将导致驻留大小与虚拟大小同步增加,从而导致实际的内存短缺。虽然泄漏虚拟内存不是一件好事,但泄漏驻留内存将对系统造成更大的影响。

看一个书里的Demo,在运行下面的应用程序时,请跟踪内存统计数据。

┌──[root@liruilongs.github.io]-[~]
└─$ watch 'free -k;grep Committed_AS /proc/meminfo'
Every 1.0s: free -k;grep Committed_AS /proc/meminfo                                             Fri Jul 29 20:47:24 2022

              total        used        free      shared  buff/cache   available
Mem:       32927488     4922536    27100148       22264      904804    27579940
Swap:      10485756           0    10485756
Committed_AS:    6853280 kB

首先安装valgrind,然后在valgrind下运行bigmem命令,要求分配256mib的常驻内存。现在bigmem请求256 MiB的虚拟内存。

┌──[root@liruilongs.github.io]-[~]
└─$ valgrind --tool=memcheck bigmem 256
==65614== Memcheck, a memory error detector
==65614== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==65614== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==65614== Command: bigmem 256
==65614==
Attempting to allocate 256 Mebibytes of resident memory...
Press <Enter> to exit

现在我们确定bigmem正在泄漏内存,泄露的原因是bigmem 256 导致的

==65614== Command: bigmem 256
==65614==
Attempting to allocate 256 Mebibytes of resident memory...

观察内存变化,Committed_AS ,即分配的虚拟内存增加了

Every 1.0s: free -k;grep Committed_AS /proc/meminfo                                             Fri Jul 29 21:00:49 2022

              total        used        free      shared  buff/cache   available
Mem:       32927488     5157320    26858220       22296      911948    27345124
Swap:      10485756           0    10485756
Committed_AS:    7281208 kB

交换空间调优

嗯,这里为了方面,我们把交换分区,交换文件统一称交换分区,或者交换空间

交换分区会增加系统上的有效内存量。当可用内存减少时,可以将不使用的页面换出到磁盘,以释放空间供其他用途。当再次需要这些页时,会发生一个严重的页错误,在使用它们之前,需要将它们再次从磁盘页到内存中。

这为我们提供了另一种方法来释放正在运行的系统上的内存,并有效地使用我们拥有的内存。swap的缺点是,与RAM相比,大多数存储设备都非常慢。在内存中进行换出和换出会显著降低系统的速度。

vmstat实用程序可以为您提供关于系统是否正在进行分页交换(“交换”)的信息。vmstat输出中的关键列是si/so等等。每秒换入的页面和每秒换出的页面。

┌──[root@liruilongs.github.io]-[~]
└─$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 27056464   3104 912452    0    0    28    20  259  310  4  2 94  0  0
 0  0      0 27055988   3104 912456    0    0     0     0 1583 2023  6  2 93  0  0
 1  0      0 27044272   3104 912480    0    0    24    36  845 1214  1  1 98  0  0
 1  0      0 27055892   3104 912480    0    0     0     0 1880 2929  5  2 94  0  0
 4  0      0 27056416   3104 912484    0    0     0     0 1730 2107  6  2 92  0  0
┌──[root@liruilongs.github.io]-[~]
└─$

将内存页面放置在交换分区中不会损害性能,它可以通过将不需要的页面移出RAM,以便将RAM用于更有用的事情来提高性能。

影响性能的是频繁地将页面移进移出交换区:si和so报告了这一点。一个系统应该有多少互换?人们提出了许多经验法则。如果你仔细想想,你需要交换两个基本的东西。

首先,移动你不需要在内存中所有时间的页面,以便RAM可以用于更好的事情。
其次,提供应急储备,避免出现内存不足的情况。空间大小很大程度上取决于系统有多少RAM以及你在计算机上做什么。

因为这比经验法则更难计算,人们倾向于根据粗略的估计来设置交换分区。Red Hat提供的基本指导如下:

系统内存最大支持 推荐最小交换分区
4gb 至少2gb
4gb ~ 16gb 至少4gb
16gb ~ 64gb 至少8gb
64gb ~ 256gb 至少16gb

系统内存和页缓存进程并不是系统内存的唯一消耗者。内核可以为自己的代码使用内存,或者以其他方式加速系统。

其中一种方法是页缓存。当您运行vmstat或free命令时,即使在拥有大量内存的系统上,也没有多少内存被标记为“空闲”。

如果系统最近执行了大量的存储I/O,这一点尤其正确。其中一个原因是页缓存。内核使用大部分未分配的内存作为缓存来存储从磁盘读取或写入的数据。

下一次需要数据时,可以从RAM而不是磁盘中获取数据。这通常会带来显著的性能改进,因为存储通常比物理内存慢得多。保留少量RAM以备短期请求使用;从长远来看,页缓存很容易被释放用于其他用途。

$ free -h
              total        used        free      shared  buff/cache   available
Mem:            15G        6.2G        316M        801M        9.0G        8.0G
Swap:          6.0G        107M        5.9G
  • swpd :当前交换到硬盘的内存总量
  • free :未被操作系统或应用程序使用的物理内存总量
  • buff : 系统缓冲区大小(单位为KB),或用于存放等待保存到硬盘的数据的内存大小(单位为KB)。该存储区允许应用程序向Linux内核发出写调用后立即继续执行(而不是等待直到数据被提交到硬盘)
  • cache :用于保存之前从硬盘读取的数据的系统高速缓存或内存的大小(单位为KB)。如果应用程序再次需要该数据,内核可以从内存而非硬盘抓取数据,由此可提高性能
$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0 110304 323928     80 9453436    0    0     0     2    0    0  1  1 97  1  0
 0  0 110304 322804     80 9453436    0    0     0     0 16190 24907  1  2 96  1  0
 0  0 110304 323900     80 9453492    0    0     0     4 15462 24521  1  1 97  1  0
 0  0 110304 324232     80 9453492    0    0     0     0 14469 23522  1  1 97  1  0
 0  0 110304 324476     80 9453540    0    0     0    16 15790 24329  1  2 95  1  0
[iomyw@iomywapp1 ~]$ 

温习一下交换空间的创建

交换分区

为你的系统额外添加一个 512MiB 的交换分区,此交换分区应在系统启动时自动挂载,不删除或以任何方式改动系统上原有的交换分区。

$ fdisk /dev/vdb  #需要分区的硬盘
.. ..
Command (m for help): n #添加新分区
Partition number (2-128, default 2):  # 直接回车(默认)
First sector (4194304-20971486, default 4194304):  # 直接回车(默认)
Last sector, *sectors or +size{K,M,G,T,P} (4194304-20971486,default 20971486): +512M
Created a new partition 2 of type 'Linux filesystem' and of size 512 MiB.
Command (m for help): w  # 保存分区表,并退出
The partition table has been altered.
Syncing disks.
$ partprobe /dev/vdb  # 刷新分区表
$ mkswap /dev/vdb2  # 格式化自建分区 vdb2
$ vim /etc/fstab
/dev/vdb2 swap swap defaults 0 0
$ swapon -a  # 启用 fstab 中的交换设备
$ swapon -s  # 查看交换分区信息

交换文件

创建一个 1G 的交换文件,系统启动时挂载

┌──[root@liruilongs.github.io]-[~]
└─$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0  200G  0 disk
├─sda1   8:1    0  150G  0 part /
└─sda2   8:2    0   10G  0 part [SWAP]
┌──[root@liruilongs.github.io]-[/dev]
└─$ dd if=/dev/sda1 of=/swap_LRL bs=1024 count=1048576
记录了1048576+0 的读入
记录了1048576+0 的写出
1073741824字节(1.1 GB)已复制,7.93545 秒,135 MB/秒
┌──[root@liruilongs.github.io]-[/dev]
└─$ mkswap  /swap_LRL
mkswap: /swap_LRL: warning: wiping old xfs signature.
正在设置交换空间版本 1,大小 = 1048572 KiB
无标签,UUID=ddecd087-fa65-4047-860b-609476a942f3
┌──[root@liruilongs.github.io]-[/]
└─$ vim /etc/fstab
┌──[root@liruilongs.github.io]-[/dev]
└─$ cat /etc/fstab  | grep LRL
/swap_LRL                    swap  swap     defaults        0 0
┌──[root@liruilongs.github.io]-[/]
└─$ swapon -a
swapon: /swap_LRL:不安全的权限 0644,建议使用 0600。
┌──[root@liruilongs.github.io]-[/]
└─$ swapon -s
文件名                          类型            大小    已用    权限
/dev/sda2                               partition       10485756        0       -2
/swap_LRL                               file    1048572 0       -3

调整内核交换的方式

当内核想要释放一页内存时,它需要在两种选择之间进行权衡。它可以从进程内存中交换一个页(放到交换分区),也可以从页缓存中删除一个页。为了做出这个决定,内核将执行以下计算:

swap_tendency = mapped_ratio/2 + distress + vm_swappiness

如果swap_tendency小于100,内核将从页缓存中回收一个页,如果大于或等于100,属于进程内存空间的页将符合交换条件。

在这个计算中,

  • mapped_ratio是使用的物理内存的百分比。
  • Distress是衡量内核在释放内存方面有多大困难的指标。它将从0开始,但如果需要更多的尝试来释放内存,它将增加(到最大100)。
  • vm_swappiness 值来自sysctl vm.swappiness即位于内核参数中的一个值,交换分区的采用频率

关于 vm.swappiness 调优。交换空间会严重影响系统。

  • 设置vm.swappiness切换到100,系统几乎总是倾向于换出页面,而不是从页面缓存中回收页面。这将使用更多的内存用于页缓存,这可以大大提高I/O繁重工作负载的性能,个人理解有更多内存空间用于buff和cache,所以I/O。
  • 设置为0将迫使系统尽可能少地进行交换。这可能会使系统响应速度更快,但以牺牲文件系统性能为代价.因为没有更多的内存用过buff和cache,大量的IO操作会造成IO阻塞。

这里我们简单温习一下,Linux内核参数如何调整

sysctl -a 查看所有内核参数:

┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ sysctl -a  # 查看所有调优参数
abi.vsyscall32 = 1
crypto.fips_enabled = 0
debug.exception-trace = 1
debug.kprobes-optimization = 1
debug.panic_on_rcu_stall = 0
dev.hpet.max-user-freq = 64
..............

cat 根据变量找对应参数文件

┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ sysctl -a | grep net.ipv4.ip_forward
net.ipv4.ip_forward = 1
net.ipv4.ip_forward_use_pmtu = 0
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ cd ../net/ipv
ipv4/ ipv6/
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ cd ../net/ipv4/
┌──[root@liruilongs.github.io]-[/proc/sys/net/ipv4]
└─$ cat ip_forward
1

设置内核参数:临时调整

/proc 目录下的数据是存放在内存中数据,每次重启就没了。

┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ cat swappiness
30
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ echo 40 > swappiness  ## 临时调整
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ cat swappiness
40

设置调优参数:永久调整

┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ echo "vm.swappiness = 20" >>  /etc/sysctl.conf  ## 永久调整
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ sysctl -p
net.ipv4.ip_forward = 1
vm.swappiness = 20
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$

下面是vm.swappiness 参数修改后vmstat的两个输出示例。

  • 第一个图显示了在内存压力下更倾向于交换的系统
  • 第二个图显示了更倾向于收缩页缓存的系统

交换分区和文件调优

交换分区性能在很大程度上受到交换分区的位置和数量的影响。

  • 在旋转的硬盘驱动器(机械硬盘)上,由于ZCAV效应,将交换分区放置在盘片的外缘比将其放置在中心附近将提供更好的吞吐量
  • SSD存储(固态硬盘)上放置交换空间可能会由于其较低的延迟和较高的吞吐量而导致更好的性能。

需要注意的是,如果您使用SSD作为交换分区,并且交换分区经常使用,那么您应该确保您的设备支持适当的磨损均衡(基于SLC-的存储可能优于基于MLC的存储,SLC的特点是寿命长,同样规格的MLC寿命比SLC要低,性能则相反)否则,设备可能会过早磨损。

使用mkswap创建多个交换空间。它们可能基于磁盘分区文件。由于内核映射交换文件的方式,只要交换文件没有碎片化交换文件和交换分区的性能应该大致相似

当使用多个交换分区时,可以使用挂载选项pri=value来指定每个空间的使用优先级。

$ cat /etc/fstab |grep -i swap|grep -i mpath
/dev/mapper/mpath18        swap                    swap    sw,pri=60        0 0
  • 与较低数字的交换分区将首先被填满,然后再移动到较高的数字。通过这种方式,更快的磁盘可以优先于较慢的磁盘
  • 当以相同的优先级激活多个交换分区时,将以轮询方式使用它们,从而减少每个交换分区的访问次数,从而获得更好的性能。

内存回收

Linux 物理内存需要不时地回收,以防止内存被填满,从而导致系统不可用。

脏内存和非活动内存回收

在我们了解内存是如何回收的之前,我们必须首先了解内存页可能处于的不同状态。这些不同的状态是:

  • Free: 页面可以立即分配,空闲的内存;
  • inactive Clean: 该页处于非活动使用状态,其内容与磁盘上的内容相对应,因为它已经被回写或自读取以来没有更改
  • inactive Dirty: 该页不是活跃使用,但该页的内容已经被修改,从磁盘读取后,还没有写回
  • Active: 该页面正在活跃使用中,并且不是被释放的候选页面

当需要分配新页面时,标记为 inactive Clean 的页面可以被视为空闲页面,但是如果拥有该页面的进程以后再次需要它,就会发生重大的页面错误

通过 /proc/meminfo 可以获得系统范围内内存分配的概览。我们可以处理的是Inactive(file)Dirty:。

$ cat /proc/meminfo 
MemTotal:       33011552 kB
MemFree:        19579840 kB
MemAvailable:   22517876 kB
Buffers:             304 kB
Cached:          3497240 kB
SwapCached:        35568 kB
Active:          8894452 kB
Inactive:        2717480 kB
Active(anon):    7107136 kB
Inactive(anon):  1869124 kB
Active(file):    1787316 kB
Inactive(file):   848356 kB
....................
Dirty:               716 kB
....................
  • Inactive(file):可以回收而不会对性能产生巨大影响的页面缓存内存,free 命令中 cache 部分的回收
  • Dirty:等待写回磁盘的内存,free 命令中 buff 部分的回收

匿名页面Inactive(anon)(与磁盘上的文件没有关联的那些页面)不能轻易释放,需要将其交换到磁盘以释放它们。

Inactive(file)Dirty的强制回收需要修改内核参数vm.drop_caches = 0

$ sysctl  -a | grep drop_caches
vm.drop_caches = 0

手动执行sync命令(sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)

┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ cat drop_caches  #缓存处理
0
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ sync

通过执行 echo 3 > /proc/sys/vm/drop_caches的方式清理,具体参数含义

To free pagecache:
    echo 1 > /proc/sys/vm/drop_caches
To free reclaimable slab objects (includes dentries and inodes):
    echo 2 > /proc/sys/vm/drop_caches
To free slab objects and pagecache:
    echo 3 > /proc/sys/vm/drop_caches
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ free -m
              total        used        free      shared  buff/cache   available
Mem:           3935         212        3357          16         366        3440
Swap:         10239           0       10239
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ echo 3 > /proc/sys/vm/drop_caches
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$ free -m
              total        used        free      shared  buff/cache   available
Mem:           3935         200        3575          16         159        3504
Swap:         10239           0       10239
┌──[root@liruilongs.github.io]-[/proc/sys/vm]
└─$

脏页写入调优

对于每个进程视图,你可以使用/proc/pid/maps.这个文件详细说明了分配给进程的每个内存段,包括共享/私有干净内存脏内存的大小。为了解析它,我们可以编写一个小的awk程序,如下所示:

cat /proc/$$/smaps | awk  '/Shared_Clean/{SHCL+=$2}/Shared_Dirty/{SHDT+=$2}/Private_Clean/{PRCL+=$2}/Private_Dirty/{PRDT+=$2}  END{ 
  print "Total Clean:",SHCL +PRCL  
  print "Total Dirty:",SHDT  +PRDT
}'
┌──[root@liruilongs.github.io]-[/dev]
└─$ cat /proc/$$/smaps | awk  '
> /Shared_Clean/{SHCL+=$2}
> /Shared_Dirty/{SHDT+=$2}
> /Private_Clean/{PRCL+=$2}
> /Private_Dirty/{PRDT+=$2}
> END{
>   print "Total Clean:",SHCL +PRCL
>   print "Total Dirty:",SHDT  +PRDT
> }'
Total Clean: 1808
Total Dirty: 1536

必须将脏页写入磁盘,以防止内存被无法释放的页填满。由于内存是易失的(断电时内容会丢失),脏页也需要写入磁盘,以防止断电时数据丢失。

在内核中,将脏页写入磁盘是由per-BDI flush线程处理的,这些线程将在必要时创建。Per-BDI flush线程将在进程列表中显示为flush-MAJOR: MINOR

有几个内核参数控制per-BDI刷新线程何时开始将数据写入磁盘。这样内核就不会因为某个进程修改了另一个字节的内存而连续地多次写入同一个页面。

┌──[root@liruilongs.github.io]-[~]
└─$ sysctl  -a | grep dirty_
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 1500
┌──[root@liruilongs.github.io]-[~]
└─$

vm.dirty_ratio = 20 :产生写操作的进程整个系统内存中处于脏状态的百分比,是绝对的脏数据限制,内存里的脏数据百分比不能超过这个值,如果超过将阻塞IO直到写出脏页

vm.dirty_background_ratio = 10 :系统总内存和脏页的百分比,即内存可以填充“脏数据”的百分比。在这个百分比事开始,内核会在后台开始写数据

vm.dirty_bytes = 0vm.dirty_background_bytes = 0 这两个参数为上面参数的byte单位时的值

vm.dirty_expire_centisecs = 3000:脏数据在符合写入磁盘条件之前必须有多旧(以1/100秒为单位),即可以存活多久。这样内核就不会因为某个进程修改了一个字节的内存而连续地多次写入同一个页面。

vm.dirty_writeback_centisecs = 1500内核唤醒刷新线程pdflush/flush/kdmflush以写入数据的频率(1/100秒)。设置为0将完全禁周期性回写

大多数调优配置文件至少修改上述设置之一。调优规则可以遵循下面的策略

  • 设置较低的比率将导致更频繁但更短的写操作,这适合交互式系统
  • 设置较高的比率将导致更少但更大的写操作,导致总体开销更小,但可能导致交互式应用程序响应时间更长.适合执行非交互的I/O任务处理,比如大文件生成之类。

内存不足处理和“OOM killer(内存杀手)”

当脏页的数据太多,同时没有可用的页面时,内核试图回收内存来满足请求。如果不能及时回收足够的内存,就会出现内存不足OOM的情况。

对于系统的级别的OOM,默认情况下,系统将启动OOM killer,选择并杀死一个或多个进程以释放内存,以便满足请求。具体的记录日志是在/var/log/messages中,如果出现了Out of memory字样,说明系统曾经出现过OOM,

在Linux内核参数中,我们可以通过vm.panic_on_oom参数来设置遇到OOM的情况,启动OOM killer的策略

如果内核参数sysctl vm.panic_on_oom设置为1而不是0,内核将会发生panic,即直接摆烂,什么时候挂掉算什么时候。默认为0.即自动启动OOM killer

┌──[root@liruilongs.github.io]-[~]
└─$ sysctl vm.panic_on_oom
vm.panic_on_oom = 0

出现内存不足的情况,就没有很多合理的恢复选项。终止进程以释放内存、放弃并终止系统或死锁都是可能的选择。

为了确定OOM杀手应该杀死哪个进程,内核为每个进程保持一个运行不良评分,可以在/proc/pid/oom_score中查看。

systemd进程的值

[root@ecs-liruilong ~]# cat /proc/1/oom_score
0

分数越高,进程越有可能被OOM杀手杀死。许多因素被用来计算这个分数:

  • VM大小(不是RSS大小),
  • 进程所有子进程的累积VM大小,
  • nice值(正的nice值会给出更高的分数),
  • 总运行时间(较长的总运行时间会降低分数),
  • 运行用户(根进程会得到轻微的保护),
  • 进程执行直接硬件访问,分数也会降低。
  • 内核本身和PID1 (sysemd)是免疫的OOM杀手。

可调的/proc/PID/oom_adj可以用来手动调整oom_score。配置该pid进程被oom killer杀掉的权重oom_adj可以的值从-17到15,其中0表示不改变(默认),越高的权重,意味着更可能被oom killer选中,-17表示免疫(永远不会杀死)。

[root@ecs-liruilong ~]# cat /proc/1/oom_adj
0

如果你希望强制的执行OOM Killer

可以echo f > /proc/sysrq-trigger,但请记住,至少会有一个进程被杀死。

[root@ecs-liruilong ~]# echo f > /proc/sysrq-trigger

Message from syslogd@ecs-liruilong at Aug  1 14:32:18 ...
 kernel:[340648.118967] Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled

输出将被发送到dmesg。

[root@ecs-liruilong ~]# cat /var/log/dmesg

博文参考

相关实践学习
2分钟自动化部署人生模拟器
本场景将带你借助云效流水线Flow实现人生模拟器小游戏的自动化部署
7天玩转云服务器
云服务器ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,可降低 IT 成本,提升运维效率。本课程手把手带你了解ECS、掌握基本操作、动手实操快照管理、镜像管理等。了解产品详情:&nbsp;https://www.aliyun.com/product/ecs
相关文章
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
11天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
8天前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能
|
11天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
16天前
|
存储 缓存 监控
|
29天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
82 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
30天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
14天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
23 0
|
21天前
|
存储 分布式计算 安全
阿里云服务器内存型r7、内存型r8y、内存型r8i实例规格性能对比与选择参考
在选择阿里云服务器实例规格时,针对内存密集型应用和数据库应用,内存型r7、内存型r8y和内存型r8i实例是这部分应用场景选择最多的热门实例规格。为了帮助大家更好地了解这三款实例的区别,并为选择提供参考,本文将详细对比它们的实例规格、CPU、内存、计算、存储、网络等方面的性能,并附上活动价格对比。让大家了解一下他们之间的不同,以供参考选择。