当前位置: 代码网 > 服务器>服务器>Linux > Linux内存泄露的原因排查和解决方案(内存管理方法)

Linux内存泄露的原因排查和解决方案(内存管理方法)

2025年02月13日 Linux 我要评论
一、问题近期我们运维同事接到线上lb(负载均衡)服务内存报警,运维同事反馈说lb集群有部分机器的内存使用率超过80%,有的甚至超过90%,而且内存使用率还再不停的增长。接到内存报警的消息,让整个团队都

一、问题

近期我们运维同事接到线上lb(负载均衡)服务内存报警,运维同事反馈说lb集群有部分机器的内存使用率超过80%,有的甚至超过90%,而且内存使用率还再不停的增长。接到内存报警的消息,让整个团队都比较紧张,我们团队负责的lb服务是零售、物流、科技等业务服务的流量入口,承接上万个服务的流量转发,一旦有故障影响业务服务比较多,必须马上着手解决内存暴涨的问题。目前只是内存报警,暂时不影响业务,先将内存使用率90%以上的lb服务下线,防止内存过高导致lb服务崩溃,影响业务,运维同事密切关注相关的内存报警的消息。

二、排查过程

经过开发同学通过cat /proc/meminfo查看slab的内核内存可能有泄漏。

$ cat /proc/meminfo
memtotal:       65922868 kb
memfree:         9001452 kb
...
slab:           39242216 kb
sreclaimable:   38506072 kb
sunreclaim:       736144 kb
....

通过slabtop命令分析slab发现内核中dentry对象占比高,考虑到dentry对象跟文件有关,linux中一切皆可以为文件,这个可能跟socket文件有关,通过进一步排查发现lb服务上有个curl发送的https探测脚本,这个脚本存在dentry对象泄漏,并且在curl论坛上找到一篇文章确认了这个问题,这个文章说明了curl-7.19.7版本在发送https请求时,curl依赖的nss库存在dentry泄漏的bug,我查看一下我们curl版本就是7.19.7,问题终于真相大白了!!!

$ curl -v
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 nss/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp
features: gss-negotiate idn ipv6 largefile ntlm ssl libz

$ rpm -aq|grep nss-
nss-util-3.16.1-3.el6.x86_64
nss-sysinit-3.16.1-14.el6.x86_64
nss-softokn-freebl-3.14.3-17.el6.x86_64
nss-softokn-3.14.3-17.el6.x86_64
nss-3.16.1-14.el6.x86_64
nss-tools-3.16.1-14.el6.x86_64

文章中介绍可以设置环境变量nss\_sdb\_use\_cache修复这个bug,我们验证通过了这个解决方案。

三、解决方案

1、目前先将探测脚本停止,在业务流量低峰时将内存使用率超过90%的服务先通过drop\_caches清理一下缓存。

2、等大促过后,探测脚本中设置环境变量nss\_sdb\_use\_cache,彻底修复这个问题。

四、内存管理方法

1)linux内存寻址

linux内核主要通过虚拟内存管理进程的地址空间,内核进程和用户进程都只会分配虚拟内存,不会分配物理内存,通过内存寻址将虚拟内存与物理内存做映射。linux内核中有三种地址,

a、逻辑地址,每个逻辑地址都由一段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。

b、线性地址,又称虚拟地址,是一个32个无符号整数,32位机器内存高达4gb,通常用十六进制数字表示,linux进程的内存一般说的都是这个内存。

c、物理地址,用于内存芯片级内存单元寻址。它们与从cpu的地址引脚发送到内存总线上的电信号对应。

linux中的内存控制单元(mmu)通过一种称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址,接着,第二个称为分页单元(paging unit)的硬件电路把线性地址转换成一个物理地址。

linux内存泄露案例分析和内存管理分享_虚拟内存

2)linux分页机制

分页单元把线性地址转换成物理地址。线性地址被分成以固定长度为单位的组,称为(page)。页内部连续的线性地址被映射到连续的物理地址中。一般"页"既指一组线性地址,又指包含这组地址中的数据。分页单元把所有的ram分成固定长度的页框(page frame),也成物理页。每一页框包含一个页(page),也就是说一个页框的长度与一个页的长度一致。页框是主存的一部分,因此也是一个存储区域。区分一页和一个页框是很重要的,前者只是一个数据块,可以存放任何页框或者磁盘中。把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并在启用分页单元之前必须有内核对页表进行适当的初始化。

x86\_64的linux内核采用4级分页模型,一般一页4k,4种页表:

a、页全局目录

b、页上级目录

c、页中间目录

d、页表

页全局目录包含若干页上级目录,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每个页表项指向一个页框。线性地址被分成5部分。

linux内存泄露案例分析和内存管理分享_虚拟内存_02

3)numa架构

随着cpu进入多核时代,多核cpu通过一条数据总线访问内存延迟很大,因此numa架构应运而生,numa架构全称为非一致性内存架构 (non uniform memory architecture),系统的物理内存被划分为几个节点(node),每个node绑定不同的cpu核,本地cpu核直接访问本地内存node节点延迟最小。

linux内存泄露案例分析和内存管理分享_页表_03

可以通过lscpu命令查看numa与cpu核的关系。

$ lscpu
architecture:          x86_64
cpu op-mode(s):        32-bit, 64-bit
byte order:            little endian
cpu(s):                32
on-line cpu(s) list:   0-31
thread(s) per core:    2
core(s) per socket:    8
socket(s):             2
numa node(s):          2
vendor id:             genuineintel
cpu family:            6
model:                 62
stepping:              4
cpu mhz:               2001.000
bogomips:              3999.43
virtualization:        vt-x
l1d cache:             32k
l1i cache:             32k
l2 cache:              256k
l3 cache:              20480k
numa node0 cpu(s):     0-7,16-23      #这些核绑定在numa 0
numa node1 cpu(s):     8-15,24-31     #这些核绑定在numa 1

4)伙伴关系算法

linux内核通过著名伙伴关系算法为分配一组连续的页框而建立一种健壮、稳定的内存分配策略,是内核中一种内存分配器,并解决了内存管理外碎片的问题,外碎片是指频繁地请求和释放不同大小的一组连续页框,必然导致在已分配的页框的块分散了许多小块的空闲页框。

5)slab机制

slab机制的核心思想是以对象的观点来管理内存,主要是为了解决内部碎片,内部碎片是由于采用固定大小的内存分区,即以固定的大小块为单位来分配,采用这种方法,进程所分配的内存可能会比所需要的大,这多余的部分便是内部碎片。slab也是内核中一种内存分配器,slab分配器基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task\_struct,file\_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。上面中说到的dentry对象就是通过slab分配器分配的一种对象。

slab和伙伴系统是上下级的调用关系,伙伴关系按照页管理内存,slab按照字节管理,slab先从伙伴系统获取数个页的内存,然后切成分成固定的小块(称为object),然后再按照声明的对象数据结构分配对象。

6)进程内存分布

所有进程都必须占用一定数量的内存,这些内存用来存放从磁盘载入的程序代码,或存放来自用户输入的数据等。内存可以提前静态分配和统一回收,也可以按需动态分配和回收。对于普通进程对应的内存空间包含5种不同的数据区:

a、代码段(text):程序代码在内存中的映射,存放函数体的二进制代码,通常用于存放程序执行代码(即cpu执行的机器指令)。

b、数据段(data):存放程序中已初始化且初值不为0的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。

c、bss段(bss):未初始化的全局变量和静态局部变量。

d、堆(heap):动态分配的内存段,大小不固定,可动态扩张(malloc等函数分配内存),或动态缩减(free等函数释放)。

e、栈(stack):存放临时创建的局部变量。

linux内存泄露案例分析和内存管理分享_虚拟内存_04

linux内核是操作系统中优先级最高的,内核函数申请内存必须及时分配适当的内存,用户态进程申请内存被认为是不紧迫的,内核尽量推迟给用户态的进程动态分配内存。

a、请求调页,推迟到进程要访问的页不在ram中时为止,引发一个缺页异常。

b、写时复制(cow),父、子进程共享页框而不是复制页框,但是共享页框不能被修改,只有当父/子进程试图改写共享页框时,内核才将共享页框复制一个新的页框并标记为可写。

7)linux内存检测工具

a、free命令可以监控系统内存

$ free -h
              total        used        free      shared  buff/cache   available
mem:           31gi        13gi       8.0gi       747mi        10gi        16gi
swap:         2.0gi       321mi       1.7gi

b、top命令查看系统内存以及进程内存

virt virtual memory size (kib):进程使用的所有虚拟内存,包括代码(code)、数据(data)、共享库(shared libraries),以及被换出(swap out)到交换区和映射了(map)但尚未使用(未载入实体内存)的部分。

res resident memory size (kib):进程所占用的所有实体内存(physical memory),不包括被换出到交换区的部分。

shr shared memory size (kib):进程可读的全部共享内存,并非所有部分都包含在 res 中。它反映了可能被其他进程共享的内存部分。

c、smaps文件

cat /proc/\$pid/smaps查看某进程虚拟内存空间的分布情况

0082f000-00852000 rw-p 0022f000 08:05 4326085    /usr/bin/nginx/sbin/nginx
size:                140 kb
rss:                 140 kb
pss:                  78 kb
shared_clean:         56 kb
shared_dirty:         68 kb
private_clean:         4 kb
private_dirty:        12 kb
referenced:          120 kb
anonymous:            80 kb
anonhugepages:         0 kb
swap:                  0 kb
kernelpagesize:        4 kb
mmupagesize:           4 kb

d、vmstat

vmstat是virtual meomory statistics(虚拟内存统计)的缩写,可实时动态监视操作系统的虚拟内存、进程、cpu活动。

## 每秒统计3次
$ vmstat 1 3
procs -----------memory---------------- ---swap-- -----io---- --system-- -----cpu-----
 r  b    swpd   free   buff  cache       si   so    bi    bo   in   cs us sy id  wa st
 0  0      0 233483840 758304 20795596    0    0     0     1    0    0  0  0 100  0  0
 0  0      0 233483936 758304 20795596    0    0     0     0 1052 1569  0  0 100  0  0
 0  0      0 233483920 758304 20795596    0    0     0     0  966 1558  0  0 100  0  0

e、meminfo文件

linux系统中/proc/meminfo这个文件用来记录了系统内存使用的详细情况。

$ cat /proc/meminfo
memtotal:        8052444 kb
memfree:         2754588 kb
memavailable:    3934252 kb
buffers:          137128 kb
cached:          1948128 kb
swapcached:            0 kb
active:          3650920 kb
inactive:        1343420 kb
active(anon):    2913304 kb
inactive(anon):   727808 kb
active(file):     737616 kb
inactive(file):   615612 kb
unevictable:         196 kb
mlocked:             196 kb
swaptotal:       8265724 kb
swapfree:        8265724 kb
dirty:               104 kb
writeback:             0 kb
anonpages:       2909332 kb
mapped:           815524 kb
shmem:            732032 kb
slab:             153096 kb
sreclaimable:      99684 kb
sunreclaim:        53412 kb
kernelstack:       14288 kb
pagetables:        62192 kb
nfs_unstable:          0 kb
bounce:                0 kb
writebacktmp:          0 kb
commitlimit:    12291944 kb
committed_as:   11398920 kb
vmalloctotal:   34359738367 kb
vmallocused:           0 kb
vmallocchunk:          0 kb
hardwarecorrupted:     0 kb
anonhugepages:   1380352 kb
cmatotal:              0 kb
cmafree:               0 kb
hugepages_total:       0
hugepages_free:        0
hugepages_rsvd:        0
hugepages_surp:        0
hugepagesize:       2048 kb
directmap4k:      201472 kb
directmap2m:     5967872 kb
directmap1g:     3145728 kb

五、总结

这次内存暴涨的问题根本原因是curl-7.19.7依赖的nss库存在dentry泄漏的bug导致的,探测脚本只是将这个问题暴露出来。这次问题由linux内存泄漏引发的问题,因此以点带面再次系统学习一下linux内存管理的知识非常有必要,对我们以后排查内存暴涨的问题非常有帮助。 

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com