要点
- 关注哪些内存指标。
- 如何定位内存相关性能问题。
- 一些常见问题分析。
基本概念
虚拟内存
虚拟内存是主存的抽象,它为每个进程和内核提供了巨大的、线性的、私有的地址空间。它具备三个能力:
1、将主存看成是一个存储在磁盘地址空间的高速缓存,主存中只保存活动区域。
2、为每个进程提供了一致的地址空间,简化了软件开发中,对存储器的管理。
3、保护每个进程的地址空间不被其他进程破坏。
匿名内存
无文件系统位置或路径名的内存,它涉及进程的私有数据:进程堆和栈。
页换出守护进程
页换出守护进程(kswapd),在当空闲内存低于某个阀值时,会被唤醒;当空闲内存高于另一个阀值时会休息。
缺页
DRAM缓存不命中称为缺页,当访问一个尚未从虚拟内存映射到物理内存的页时,会发生缺页。
MMU(内存管理单元)
负责将虚拟内存地址转换为物理内存地址,位于CPU芯片上。它按页做转换,而页内的偏移量则直接映射。
TLB(翻译后备缓冲器)
MMU(存储器管理单元)中对PTE(页表条目)的小缓存,位于CPU芯片上。发生进程上下文切换时,TLB将会进行刷新。
slab内存分配器
内核slab分配器管理特定大小的对象缓存,能被快速地回收利用,避免页分配开销。
内存申请
1、应用程序发起内存申请,如:malloc、realloc。
2、直接从空闲列表中响应请求,或者扩展虚拟内存地址空间再分配:
a.利用brk()系统调用扩展堆的尺寸。
b.利用mmap()系统调用来创建一个新的内存段地址。
3、调用CPU内部的MMU(内存管理单元),将虚拟地址转换为物理地址,若转换失败,则产生缺页错误。
4、内核从物理内存空闲列表中找到一个空闲地址,并映射到该虚拟地址。
5、当系统内存需求超过一定水平时,内核中的页换出守护进程(kswapd)就开始寻找可以释放的内存页:
a.文件系统页:从磁盘读出且没有修改过的页,如,可执行代码、数据等。
b.脏页:发生过修改的页,先写回磁盘后释放。
c.匿名页:如应用程序数据等,先存入换页设备后释放。
文件系统缓存
- 页缓存:缓存的内容是虚拟内存页,包括文件内容、I/O缓冲信息,目的在于提高文件性能和目录I/O性能。
- inode缓存:inode是文件系统用于描述所存对象的一个数据结构体。
- 目录缓存(dcache):包括目录元素名到VFS inode之间的映射信息,提高路径名查找速度。
buffer
为磁盘读写操作提供缓存,如:dd if=/dev/vda1 。在man free 中的解释为"Memory used by kernel buffers (Buffers in /proc/meminfo)"。buffer表示内核缓冲区用到的内存。
cache
为文件读写提供缓存,如:dd if=/tmp/file 。在man free 中的解释为"Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo)"。cache表示内核页缓存和Slab用到的内存。
swap
swap使得系统可用内存变大,将进程暂时不使用的内存数据,存入磁盘,称为换出;将磁盘上的数据载入内存,称为换入。
RSS(常驻集合)
已分配主存大小。
(N)UMA:(非)均匀访存模型
UMA是指,每个CPU通过共享系统总线,访问内存,所有CPU的内存访问延时较均匀;而NUMA,每个CPU其对应的内存节点,当访问其他CPU的内存节点时,需要通过CPU互联发起。因此,本地内存访问要比远程访问延时低。
分析工具
free
系统空闲和已使用内存总量。
# free -h total used free shared buff/cache availableMem: 62G 13G 779M 3.2G 48G 44GSwap: 8.0G 5.6G 2.4G
其中,
total:系统内存总量。
used:已经使用的内存量。
free:未使用的内存量。
shared:tmpfs使用的内存。
buff/cache:buffers和cache总和。
avaiable:在不进行交换的情况下,估计可用于启动新应用程序的内存大小。
vmstat
虚拟内存统计信息,包括当前内存和换页在内的系统内存健康程度。
# vmstat 1procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 0 121088 185124 1397420 0 0 3 35 3 3 1 1 98 0 0 0 0 0 120700 185124 1397452 0 0 0 100 465 987 1 1 98 0 0 0 0 0 120716 185124 1397452 0 0 0 0 392 855 0 1 99 0 0 1 0 0 120732 185124 1397452 0 0 0 0 380 832 0 1 99 0 0 0 0 0 120832 185124 1397452 0 0 0 0 490 1041 1 1 98 0 0 0 0 0 120824 185124 1397452 0 0 0 0 426 927 1 1 98 0 0 0 0 0 120840 185124 1397452 0 0 0 0 442 945 1 1 98 0 0 0 0 0 120808 185124 1397456 0 0 0 0 407 937 0 0 100 0 0
swpd:交换出的内存量。
free:空闲的可用内存。
buff:用于缓冲缓存的内存。
cache:用于页缓存的内存。
si:换入的内存。
so:换出的内存。
sar
查看内存相关的历史统计数据。
# sar -Br 1 10Linux 3.10.0-957.el7.x86_64 (localhost.localdomain) 10/29/2021 _x86_64_ (40 CPU)02:45:23 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff02:45:24 PM 0.00 44.00 14406.00 0.00 5787.00 0.00 0.00 0.00 0.0002:45:23 PM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty02:45:24 PM 13430580 18803276 58.33 566212 13082464 11630772 28.63 13024104 4105728 78402:45:24 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff02:45:25 PM 0.00 160.00 35787.00 0.00 5878.00 0.00 0.00 0.00 0.0002:45:24 PM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty02:45:25 PM 13429252 18804604 58.34 566212 13082512 11631300 28.63 13024400 4105728 820
内存统计相关的信息选项:
- -B:换页统计信息。
- -H:大页面统计信息。
- -r:内存使用率。
- -R:内存统计信息。
- -S:交换空间统计信息。
- -W:交换统计信息。
相关字段说明:
- pgpgin/s:页面换入。
- pgpgout/s:页面换出。
- fault/s:严重及轻微缺页。
- majflt/s:严重缺页。
- pgfree/s:页面加入空闲链表。
- pgscank/s:被后台页面换出守护进程扫描过的页面(kswapd)。
- pgscand/s:直接页面扫描。
- pgsteal/s:页面及交换高速缓存回收。
- %vmeff:页面回收的效率(pgsteal/pgscan),高数值表示,几乎所有的非活动页面都进行了回收(健康),低数值表示,系统在挣扎中。100%为高数值,少于30%是低数值。未扫描到任何页面则显示为0。
pmap
显示进程的内存映射,显示它们的大小、权限及映射对象。
# pmap -x 36713671: ./a.outAddress Kbytes RSS Dirty Mode Mapping0000000000400000 4 4 0 r-x-- a.out0000000000600000 4 4 4 r---- a.out0000000000601000 4 4 4 rw--- a.out0000000002165000 132 4 4 rw--- [ anon ]00007fda646cd000 1808 356 0 r-x-- libc-2.17.so00007fda64891000 2044 0 0 ----- libc-2.17.so00007fda64a90000 16 16 16 r---- libc-2.17.so00007fda64a94000 8 8 8 rw--- libc-2.17.so00007fda64a96000 20 12 12 rw--- [ anon ]00007fda64a9b000 84 12 0 r-x-- libgcc_s-4.8.5-20150702.so.100007fda64ab0000 2044 0 0 ----- libgcc_s-4.8.5-20150702.so.100007fda64caf000 4 4 4 r---- libgcc_s-4.8.5-20150702.so.100007fda64cb0000 4 4 4 rw--- libgcc_s-4.8.5-20150702.so.100007fda64cb1000 1028 64 0 r-x-- libm-2.17.so00007fda64db2000 2044 0 0 ----- libm-2.17.so00007fda64fb1000 4 4 4 r---- libm-2.17.so00007fda64fb2000 4 4 4 rw--- libm-2.17.so00007fda64fb3000 932 472 0 r-x-- libstdc++.so.6.0.1900007fda6509c000 2048 0 0 ----- libstdc++.so.6.0.1900007fda6529c000 32 32 32 r---- libstdc++.so.6.0.1900007fda652a4000 8 8 8 rw--- libstdc++.so.6.0.1900007fda652a6000 84 16 16 rw--- [ anon ]00007fda652bb000 136 108 0 r-x-- ld-2.17.so00007fda654cc000 20 20 20 rw--- [ anon ]00007fda654da000 8 8 8 rw--- [ anon ]00007fda654dc000 4 4 4 r---- ld-2.17.so00007fda654dd000 4 4 4 rw--- ld-2.17.so00007fda654de000 4 4 4 rw--- [ anon ]00007ffd7efe7000 132 16 16 rw--- [ stack ]00007ffd7f1b3000 8 4 0 r-x-- [ anon ]ffffffffff600000 4 0 0 r-x-- [ anon ]---------------- ------- ------- -------total kB 12680 1196 176
使用该命令可以找出内存瓶颈位于哪一部分。
slabtop
通过slab分配器输出内核slab缓存使用情况。
Active / Total Objects (% used) : 5107972 / 5815486 (87.8%) Active / Total Slabs (% used) : 139879 / 139879 (100.0%) Active / Total Caches (% used) : 76 / 104 (73.1%) Active / Total Size (% used) : 949466.02K / 1146425.82K (82.8%) Minimum / Average / Maximum Object : 0.01K / 0.20K / 12.75K OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME292125 170878 58% 1.01K 9427 31 301664K ext4_inode_cache2869269 2697899 94% 0.10K 73571 39 294284K buffer_head975828 974260 99% 0.19K 23234 42 185872K dentry184471 136016 73% 0.57K 6592 28 105472K radix_tree_node393728 110166 27% 0.06K 6152 64 24608K kmalloc-64 35133 33483 95% 0.64K 717 49 22944K proc_inode_cache 11440 11352 99% 2.00K 715 16 22880K kmalloc-2048 30745 30391 98% 0.58K 559 55 17888K inode_cache 93534 93534 100% 0.12K 2751 34 11004K kernfs_node_cache 16422 15846 96% 0.62K 322 51 10304K sock_inode_cache 10048 9913 98% 1.00K 314 32 10048K kmalloc-1024120904 119686 98% 0.07K 2159 56 8636K avc_node 1876 1805 96% 4.06K 268 7 8576K task_struct 12480 11804 94% 0.66K 260 48 8320K shmem_inode_cache 16576 14770 89% 0.50K 518 32 8288K kmalloc-512 4016 3732 92% 1.94K 251 16 8032K TCP 2728 2463 90% 2.69K 248 11 7936K task_xstate 37170 30499 82% 0.19K 885 42 7080K kmalloc-192 2790 2631 94% 2.06K 186 15 5952K sighand_cache121720 82094 67% 0.05K 1432 85 5728K shared_policy_node 4984 4793 96% 1.12K 178 28 5696K signal_cache 25456 24276 95% 0.21K 688 37 5504K vm_area_struct 8256 8180 99% 0.66K 172 48 5504K ovl_inode 2565 2432 94% 2.06K 171 15 5472K TCPv6 20512 18454 89% 0.25K 641 32 5128K kmalloc-256 10512 10271 97% 0.44K 292 36 4672K ip6_dst_cache 4350 4320 99% 1.06K 145 30 4640K UDP 11298 11298 100% 0.38K 269 42 4304K mnt_cache 1000 943 94% 4.00K 125 8 4000K kmalloc-4096 93942 77108 82% 0.04K 921 102 3684K ext4_extent_status 432 384 88% 8.00K 108 4 3456K kmalloc-8192 84048 84048 100% 0.04K 824 102 3296K selinux_inode_security 1335 1335 100% 2.06K 89 15 2848K idr_layer_cache 19584 18929 96% 0.12K 612 32 2448K kmalloc-128 1976 1924 97% 1.19K 76 26 2432K RAWv6 35904 35264 98% 0.06K 561 64 2244K ext4_free_data
输出包括顶部的汇总和slab列表,其中包括对象数据量(OBJS)、多少是活动的(ACTIVE)、使用百分比(USE)、对象大小(OBJ SIZE,字节)和缓存大小(CACHE SIZE,字节)。
dmesg
显示内核ring buffer内容,可以用于诊断设备故障。
valgrind
用于调试或分析程序的工具集。经常用来分析程序是否存在内存泄露。
# valgrind --tool=memcheck ./a.out==13932== Memcheck, a memory error detector==13932== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.==13932== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info==13932== Command: ./a.out==13932==^C==13932====13932== Process terminating with default action of signal 2 (SIGINT)==13932== at 0x571B8D0: __nanosleep_nocancel (in /usr/lib64/libc-2.17.so)==13932== by 0x574C1C3: usleep (in /usr/lib64/libc-2.17.so)==13932== by 0x400893: main (mcount.cpp:24)==13932====13932== HEAP SUMMARY:==13932== in use at exit: 9,772 bytes in 9,772 blocks==13932== total heap usage: 9,772 allocs, 0 frees, 9,772 bytes allocated==13932====13932== LEAK SUMMARY:==13932== definitely lost: 9,772 bytes in 9,772 blocks==13932== indirectly lost: 0 bytes in 0 blocks==13932== possibly lost: 0 bytes in 0 blocks==13932== still reachable: 0 bytes in 0 blocks==13932== suppressed: 0 bytes in 0 blocks==13932== Rerun with --leak-check=full to see details of leaked memory==13932====13932== For counts of detected and suppressed errors, rerun with: -v==13932== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
使用memcheck工具,可以查看该程序是否存在内存问题。不过,使用valgrind进行内存检测时,对服务的性能影响较大,一般不用直接在线上服务上使用。
cachestat
BCC工具集,显示整个系统的缓存命中情况。
# ./cachestat 1 5 HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB 6688 1 1409 99.99% 553 12820 6597 0 1406 100.00% 553 12820 6495 0 1368 100.00% 553 12820 6712 0 1450 100.00% 553 12820 6748 0 1441 100.00% 553 12820
cachetop
BBC工具集,显示每个进程读写的page cache命中情况。
16:36:57 Buffers MB: 181 / Cached MB: 1208 / Sort: HITS / Order: descendingPID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 17020 root lsblk 391 0 0 100.0% 0.0% 17011 root sh 305 0 0 100.0% 0.0% 17012 root sh 301 0 0 100.0% 0.0% 17019 root sh 298 0 0 100.0% 0.0% 17021 root awk 276 0 0 100.0% 0.0% 17014 root awk 260 0 0 100.0% 0.0% 17011 root cat 146 0 0 100.0% 0.0% 17013 root cat 146 0 0 100.0% 0.0% 17019 root barad_agent 137 0 0 100.0% 0.0% 17020 root sh 106 0 0 100.0% 0.0% 17021 root sh 103 0 0 100.0% 0.0%
memleak
BBC工具集,用于定位内存泄露问题。
# ./memleak -p 21311Attaching to pid 21311, Ctrl+C to quit.[17:00:13] Top 10 stacks with outstanding allocations: 27566 bytes in 27566 allocations from stack operator new(unsigned long)+0x1d [libstdc++.so.6.0.19] main+0x9 [a.out] __libc_start_main+0xf5 [libc-2.17.so][17:00:19] Top 10 stacks with outstanding allocations: 56396 bytes in 56396 allocations from stack operator new(unsigned long)+0x1d [libstdc++.so.6.0.19] main+0x9 [a.out] __libc_start_main+0xf5 [libc-2.17.so]
从执行结果,可以看出,该程序存在内存泄露的堆栈。
分析策略
1、检查系统信息中是否有OOM Killer杀掉进程的信息:dmesg。
2、检查系统中是否配置了换页设备,以及使用的换页空间大小;并且检查这些换页设备是否有活跃的I/O操作:iostat、vmstat。
3、检查系统中空闲内存的数量,以及整个系统的缓存使用情况:free。
4、按进程检查内存用量:top、ps。
5、检查系统中缺页错误的发生频率,并且检查缺页错误发生时的调用栈信息,这可以解释RSS增长的原因。
6、检查缺页错误和哪些文件有关。
7、跟踪brk()和mmap()调用来从另一个角度审查内存用量。
8、使用PMC测量硬件缓存命空率和内存访问,分析导致内存I/O发生的函数和指令信息:perf。
常见问题
swap活跃
目前的服务器上内存基本充足,因此,大多数服务器上,会将swap关闭。这样可以避免在非必要时刻,触发系统换入换出,从而引起性能问题。
涉及swap的常用操作:
调整swap活跃性:修改/proc/sys/vm/swappiness,取值范围为0~100,值越大越活跃。
关闭swap:swapoff。
开启swap:swapon。
内存泄露
如果条件允许,可以使用valgrind来进行检测,从而定位到存在内存泄露的代码。但是,很多时候,是线上服务发现了内存泄露,不允许随意重启。此时,可以考虑使用memleak来进行跟踪。需要注意的是,无论是valgrind,还是memleak,对服务的性能会有一定的影响。另外,还可以使用pmap找出内存泄漏的内存段,然后,dump出该内存段的内容分析。也可以从代码管理的角度,分析最近的更新记录,找出可能的泄露点。
造成内存泄露的本质原因是,内存申请后未释放。这种未释放,可能是显式调用malloc申请了一块内存,使用完之后,未调用free释放。也可能是,一个常驻内存的全局变量,一直未释放,比如,有一个全局的vector对象,一直在往里面放数据,而从未清空,也会造成内存泄露。
在自己管理内存时,应当遵循"谁申请、谁释放"的原则,使用RAII(资源获取即初始化)的方式进行内存管理,在一定程度上,这样可以避免内存泄漏问题。
core dump
程序出现core dump,通常情况下,是由于进程访问了自身以外的内存空间,或者是访问到了只读地址。出现core dump时,通常的定位方法有:
1、通过gdb调试core文件,查看进程的堆栈,找出问题。
2、如果堆栈已经破坏,确认下是否可以重建堆栈。重建堆栈的原理是,old $RBP中,保存着上一个调用的$RBP地址,同时,8(%RBP)中,保存着old $EIP,通过重置寄存器的值,可以看到一部分调用堆栈。
3、从代码管理角度出发,找出最近的更新记录,分析运行日志,定位问题。
参考
《Systems Performance:Enterprise and Cloud》
《BPF Performance Tools》
《Computer Systems》
《Modern Operating Systems》
http://www.brendangregg.com/linuxperf.html
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/81739.html