e0005055@ibudev20:~$ free
total used free shared buff/cache available
Mem: 32791720 19499516 935896 2552 12356308 12824920
Swap: 2097148 2048 2095100
其中,total ≈ used+available
~$ sudo cat /proc/buddyinfo
Node 0, zone DMA 1 1 1 0 2 1 1 0 1 1 3
Node 0, zone DMA32 7437 3964 2908 225 83 19 8 2 0 0 0
Node 0, zone Normal 64044 11133 27161 593 116 3 0 0 0 0 0
从buddy可以看到mem区域和对应的使用情况。64系统因为寻址空间大,就不存在高端内存了。
~$ sudo cat /proc/slabinfo
slabinfo - version: 2.1
# name : tunables : slabdata
uvm_tools_event_tracker_t 0 0 1128 29 8 : tunables 0 0 0 : slabdata 0 0 0
uvm_range_group_range_t 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0
uvm_va_block_context_t 0 0 1472 22 8 : tunables 0 0 0 : slabdata 0 0 0
uvm_va_block_gpu_state_t 0 0 432 37 4 : tunables 0 0 0 : slabdata 0 0 0
uvm_va_block_t 0 0 784 20 4 : tunables 0 0 0 : slabdata 0 0 0
uvm_va_range_t 0 0 1912 17 8 : tunables 0 0 0 : slabdata 0 0 0
btrfs_delayed_node 0 0 312 26 2 : tunables 0 0 0 : slabdata 0 0 0
btrfs_ordered_extent 0 0 416 39 4 : tunables 0 0 0 : slabdata 0 0 0
btrfs_inode 0 0 1168 28 8 : tunables 0 0 0 : slabdata 0 0 0
ufs_inode_cache 0 0 808 20 4 : tunables 0 0 0 : slabdata 0 0 0
qnx4_inode_cache 0 0 680 24 4 : tunables 0 0 0 : slabdata 0 0 0
:~$ sudo cat /proc/zoneinfo
Node 0, zone DMA
...
Node 0, zone Normal
pages free 201343
min 15777
low 23415
high 31053
spanned 7790592
present 7790592
managed 7640422
protection: (0, 0, 0, 0, 0)
nr_free_pages 201343
nr_zone_inactive_anon 65772
nr_zone_active_anon 56559
...
cpu: 7
count: 332
high: 378
batch: 63
vm stats threshold: 72
node_unreclaimable: 0
start_pfn: 1048576
...
~$ cat /proc/meminfo
MemTotal: 32791720 kB
MemFree: 930456 kB
MemAvailable: 12815656 kB
Buffers: 2036752 kB
Cached: 6727356 kB
SwapCached: 244 kB
Active: 5784032 kB
Inactive: 3470456 kB
Active(anon): 225796 kB
Inactive(anon): 263096 kB
...
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 23929072 kB
DirectMap2M: 9529344 kB
DirectMap1G: 0 kB
~$ sudo cat /proc/vmallocinfo
0xffffbc3d00000000-0xffffbc3d00005000 20480 irq_init_percpu_irqstack+0xcf/0x100 vmap
0xffffbc3d00005000-0xffffbc3d00007000 8192 acpi_os_map_iomem+0x17c/0x1b0 phys=0x000000008dbfe000 ioremap
...
0xffffbc3d0002c000-0xffffbc3d0002e000 8192 gen_pool_add_owner+0x42/0xb0 pages=1 vmalloc N0=1
0xffffbc3d0002e000-0xffffbc3d00030000 8192 bpf_prog_alloc_no_stats+0x4c/0xf0 pages=1 vmalloc N0=1
0xffffbc3d00030000-0xffffbc3d00035000 20480 _do_fork+0x76/0x370 pages=4 vmalloc N0=4
...
0xffffbc3d00065000-0xffffbc3d00068000 12288 pcpu_mem_zalloc+0x48/0x70 pages=2 vmalloc N0=2
0xffffbc3d00068000-0xffffbc3d0006c000 16384 n_tty_open+0x19/0xa0 pages=3 vmalloc N0=3
很多参数在内核编译时开启VM_EVENT_COUNTERS,因为这部分仅仅用于调试和统计。
$ cat /proc/vmstat
参考:
https://www.kernel.org/doc/Documentation/vm/transhuge.txt
vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。
是对系统的整体情况进行统计,无法进行某个进程的分析。
$ vmstat 5 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 26016976 972420 5206800 0 0 3 9 30 4 1 0 99 0 0
0 0 0 26016716 972428 5206836 0 0 0 4 275 428 0 0 99 0 0
0 0 0 26017284 972428 5206836 0 0 0 0 324 513 0 0 99 0 0
0 0 0 26016992 972436 5206828 0 0 0 2 299 493 0 0 99 0 0
0 0 0 26016512 972436 5206836 0 0 0 0 265 440 0 0 99 0 0
参考:https://www.cnblogs.com/ftl1012/p/vmstat.html
pmap [pid]
$ pmap 13713
13713: ./a.out
0000000000400000 4K r-x-- a.out
0000000000600000 4K r---- a.out
0000000000601000 4K rw--- a.out
0000000002315000 132K rw--- [ anon ]
00007f7e2ee73000 1948K r-x-- libc-2.27.so
00007f7e2f05a000 2048K ----- libc-2.27.so
00007f7e2f25a000 16K r---- libc-2.27.so
00007f7e2f25e000 8K rw--- libc-2.27.so
00007f7e2f260000 16K rw--- [ anon ]
00007f7e2f264000 164K r-x-- ld-2.27.so
00007f7e2f46b000 8K rw--- [ anon ]
00007f7e2f48d000 4K r---- ld-2.27.so
00007f7e2f48e000 4K rw--- ld-2.27.so
00007f7e2f48f000 4K rw--- [ anon ]
00007ffe47bad000 132K rw--- [ stack ]
00007ffe47bf8000 12K r---- [ anon ]
00007ffe47bfb000 4K r-x-- [ anon ]
ffffffffff600000 4K --x-- [ anon ]
total 4516K
循环显示最后一行:
while true; do pmap -d 2173 | tail -1; sleep 2; done
pmap可以显示swap和匿名页等更多信息,使用pmap --help参看细节.
$ pmap -p 25991 -XX
25991: /home/e0005055/wk/test/mm/a.out
Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlagsMapping
00400000 r-xp 00000000 103:01 17050969 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd /home/e0005055/wk/test/mm/a.out
00600000 r--p 00000000 103:01 17050969 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd /home/e0005055/wk/test/mm/a.out
00601000 rw-p 00001000 103:01 17050969 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd /home/e0005055/wk/test/mm/a.out
01c1e000 rw-p 00000000 00:00 0 132 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd [heap]
7f5ec0f32000 r-xp 00000000 103:01 49681351 1948 4 4 1208 11 1208 0 0 0 1208 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me sd /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec1119000 ---p 001e7000 103:01 49681351 2048 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mr mw me sd /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec1319000 r--p 001e7000 103:01 49681351 16 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac sd /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec131d000 rw-p 001eb000 103:01 49681351 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec131f000 rw-p 00000000 00:00 0 16 4 4 12 12 0 0 0 12 12 12 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd
7f5ec1323000 r-xp 00000000 103:01 49681347 164 4 4 164 1 164 0 0 0 164 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec152a000 rw-p 00000000 00:00 0 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd
7f5ec154c000 r--p 00029000 103:01 49681347 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec154d000 rw-p 0002a000 103:01 49681347 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec154e000 rw-p 00000000 00:00 0 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd
7ffd2b423000 rw-p 00000000 00:00 0 132 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me gd ac [stack]
7ffd2b583000 r--p 00000000 00:00 0 12 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 rd mr pf io de dd sd [vvar]
7ffd2b586000 r-xp 00000000 00:00 0 4 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me de sd [vdso]
ffffffffff600000 --xp 00000000 00:00 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ex [vsyscall]
==== ============== =========== ==== === ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== ===========
4516 72 72 1464 100 1376 0 4 84 1464 84 0 0 0 0 0 0 0 0 0 0 KB
$ cat /proc/25991/maps
00400000-00401000 r-xp 00000000 103:01 17050969 /home/e0005055/wk/test/mm/a.out
00600000-00601000 r--p 00000000 103:01 17050969 /home/e0005055/wk/test/mm/a.out
00601000-00602000 rw-p 00001000 103:01 17050969 /home/e0005055/wk/test/mm/a.out
01c1e000-01c3f000 rw-p 00000000 00:00 0 [heap]
7f5ec0f32000-7f5ec1119000 r-xp 00000000 103:01 49681351 /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec1119000-7f5ec1319000 ---p 001e7000 103:01 49681351 /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec1319000-7f5ec131d000 r--p 001e7000 103:01 49681351 /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec131d000-7f5ec131f000 rw-p 001eb000 103:01 49681351 /lib/x86_64-linux-gnu/libc-2.27.so
7f5ec131f000-7f5ec1323000 rw-p 00000000 00:00 0
7f5ec1323000-7f5ec134c000 r-xp 00000000 103:01 49681347 /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec152a000-7f5ec152c000 rw-p 00000000 00:00 0
7f5ec154c000-7f5ec154d000 r--p 00029000 103:01 49681347 /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec154d000-7f5ec154e000 rw-p 0002a000 103:01 49681347 /lib/x86_64-linux-gnu/ld-2.27.so
7f5ec154e000-7f5ec154f000 rw-p 00000000 00:00 0
7ffd2b423000-7ffd2b444000 rw-p 00000000 00:00 0 [stack]
7ffd2b583000-7ffd2b586000 r--p 00000000 00:00 0 [vvar]
7ffd2b586000-7ffd2b587000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
详细描述每一个段的信息,和pmap命令差不多。
$ cat /proc/25991/smaps
00400000-00401000 r-xp 00000000 103:01 17050969 /home/e0005055/wk/test/mm/a.out
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
...
THPeligible: 0
VmFlags: rd ex mr mw me dw sd
00600000-00601000 r--p 00000000 103:01 17050969 /home/e0005055/wk/test/mm/a.out
Size: 4 kB
KernelPageSize: 4 kB
... ...
smem是通过/proc/PID/smaps分析系统内存
$ smem
PID User Command Swap USS PSS RSS
25991 e0005055 /home/e0005055/wk/test/mm/a 0 88 100 1464
17626 e0005055 script test.sh_tmp.log 0 172 212 2812
4398 e0005055 /usr/bin/dbus-daemon --conf 0 424 514 3840
5274 e0005055 /usr/lib/dconf/dconf-servic 0 688 766 5148
4296 e0005055 /usr/lib/x86_64-linux-gnu/x 0 680 769 5272
4384 e0005055 /usr/lib/at-spi2-core/at-sp 0 760 859 6280
...
procrank是通过/proc/kpagemap分析,暂未实践。
overcommit为超量使用,申请内存大小超过当前已申请(并非已分配)内存和swap的总和,仍然允许申请(并非分配),称为超量使用。
在meminfo中两个值:
~$ cat /proc/meminfo
...
CommitLimit: 18493008 kB
Committed_AS: 3567204 kB
CommitLimit为overcommit的阈值,申请的内存总数超过CommitLimit的话就算是overcommit。
Committed_AS 表示所有进程已经申请的内存总大小,如果超过CommitLimit就是已经发生overcommit。
CommitLimit既不是物理内存的大小,也不是free memory的大小,它是通过内核参数vm.overcommit_ratio或vm.overcommit_kbytes间接设置的,公式如下:
【CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap】
overcommit_ratio是设定的超量使用比例,默认为50,可以通过overcommit_ratio查看:
~$ cat /proc/sys/vm/overcommit_ratio
50
超量使用可以通过以下方式查看:
~$ cat /proc/sys/vm/overcommit_memory
0
其中,0为默认值,允许超量使用,但是不能超过系统总内存。1为无限制超量使用,2为关闭超量使用。
sudo sh -c ‘echo 1 \> /proc/sys/vm/overconmit_memory’
其它配置方式:
$ vim /etc/sysctl.conf
vm.overcommit_memory=1
$ sysctl -p
$ sysctl vm.overcommit_memory=1
或者:
$ sysctl -w vm.overcommit_memory=1
参考:
https://access.redhat.com/solutions/665023
http://linuxperf.com/?p=102
内存耗尽时,会根据当前进程的oom_score值来决定某一进程结束:
oom_score会加上oom_score_adj这个值,oom_score_adj的取值范围是-1000~1000,
$ cat /proc/898/oom_score
0
$ cat /proc/898/oom_score_adj
-999
oom_adj是调整进程优先级,序号越靠后,优先级越低,oom_score越大。范围【-17,16】默认为0,系统级进程为负值。
oom_adj是一个旧的接口参数,其功能类似oom_score_adj,保留时为了兼容,其实最后也是算oom_score_adj。
$ cat /proc/898/oom_adj
-16
OOM测试时,需要关闭swap分区,关闭overcommit:
sudo swapoff -a
sudo sh -c ‘echo 1 \> /proc/sys/vm/overconmit_memory’
git grep overcommit_memory
普通权限可以将oom_adj往后调整,但需要管理员权限才能往前调整:
$ cat /proc/18092/oom_adj
0
$ echo 13 > /proc/18092/oom_adj
$ cat /proc/18092/oom_adj
12
$ echo 3 > /proc/18092/oom_adj
-bash: echo: write error: Permission denied
$ sudo echo 3 > /proc/18092/oom_adj
$ cat /proc/18092/oom_adj
2
/proc/sys/vm/panic_on_oom设定oom发生时,采取的策略。
当该参数等于0的时候,启动OOM killer回收内存。当该参数等于2的时候,强制kernel panic宕机。
$ cat /proc/sys/vm/panic_on_oom
0
/proc/sys/vm/oom_dump_tasks设定是否在oom时打印所有进程的内存信息,设置非0时会打印。
$ cat /proc/sys/vm/oom_dump_tasks
1
参考:https://blog.csdn.net/u011677209/article/details/52769225
内存泄露通过长时间观察内存状态,占用持续上升。
除内存泄露之外,可以检测多线程、缓存、堆栈等相关问题。
Address Sanitizer是基于LLVM的的工具,已经内置在GCC4.8以上版本。使用时大概多出2倍的运行开销,属于比较快速的工具。
Address Sanitizer替换了malloc和free的实现。根据内存分配和释放操作,将不可访问区域标记为”off-limits“,当访问到这些标记内存区域时,报告异常。
Address Sanitizer可以用来检测如下内存使用错误:
内存释放后又被使用;
内存重复释放;
释放未申请的内存;
使用栈内存作为函数返回值;
使用了超出作用域的栈内存;
内存越界访问;
需要在程序编译时使用 -fsanitize=address、-fsanitize=leaks,-fno-omit-frame-pointer用于额外的堆栈信息。之后再运行,出现问题时会报错。
cppcheck是一个C/C++静态检查工具。协助侦测代码级别的问题,比如数组越界、内存申请未释放、文件打开未关闭等。
大部分IDE都有用于静态检测的插件,比如SourceInsight\Eclipse\VS Code , 可以在互联网搜索相关信息。
有时候内存泄露并不是应用造成的,而是依赖的对应内核模组产生的。kmemleak是Linux内核自带的检测工具,不过使用该功能需要在内核编译时需要打开kmemleaks选项,重新编译内核。
mtrace是GNU扩展函数,mtrace为内存分配函数(malloc, realloc, memalign, free)安装hook函数。
使用mtrace需要在代码里添加mtrace函数重新编译运行,在运行停止之后会打印出尚未释放的内存信息,然后用mtrace分析log,找到对应的函数。之后查看代码,分析其调用信息修改。
工程调试内存泄漏问题一般步骤:
meminfo, free 多点采样
使用多点采样,确认是否有内存泄漏。
定位程序
通过smem等方式,检查用户空间,找到可疑的应用程序
检查内核空间
通过slab等信息判断内核是否存在泄露,通过kmemleak分析内核泄露信息。
通过buddy、slab、meminfo等系统信息来辅助分析。
free 命令中,buff是指文件IO时的缓存,cache是上层文件系统的缓存。
不带pagecached的IO为direct IO。 可以在用户态根据业务特点做cached,在某些场合上会在应用层来建立对应的访问逻辑。
类似于CPU跳过cache,直接访问mem;
在内核配置CONFIG_SWAP,支持匿名页swap。不配置,普通文件swap依然支持;
SWAP分区,对应windows的虚拟内存文件pagefile.system。
局部性原理:最近活跃的就是将来活跃的,最近不活跃的,以后也不活跃。
包括时间局部性,空间局部性。
LRU:Least Recently Used最近最少使用,是一种常用的页面置换算法。
swap匿名页可以理解,是将数据交换到磁盘上的swap区域。
但是普通文件swap,究竟交换的是什么?比如一个so文件,swap到swap分区,和直接与库文件原先的位置来加载有啥区别?
答案:普通文件不需要swap,文件页本身就是可以回收的,不需要放到匿名页。swap只是针对匿名页,原本不可回收的、没有任何文件背景的数据。
嵌入式设备,一般不用swap,不使能swap,因为:
1.嵌入式磁盘速度很慢;
2.FLASH读写寿命有限;
zRAM的功能就是将RAM划分一部分区域,将RAM中数据压缩放入该区域来swap。这就实现了用CPU算力来换取空间。
#内核使能swap
echo $((48*1024*1024)) > /sys/block/zram0/disksize
打开swap分区:
swapon –p 10 /dev/zram0
cat /proc/swaps
swapoff –a 不能关掉文件背景页面。
脏页回写是由pdfulsh执行的。其运行周期dirty_writeback_centisecs,单位厘秒:
$ cat /proc/sys/vm/dirty_writeback_centisecs
500
脏页回写有两个机制:
$ cat /proc/sys/vm/dirty_expire_centisecs
3000
$ cat /proc/sys/vm/dirty_background_ratio
10
当dirty_expire_centisecss设置较小,刷新频率就会增加,这样就会使得脏数据所占总内存的比例不会达到dirty_background_ratio,从而使得dirty_background_ratio参数没有什么作用。
相反,如果dirty_background_ratio参数设置很小同时dirty_expire_centisecs设置较大,dirty_expire_centisecss可能也用不上。
$ cat /proc/sys/vm/dirty_ratio
20
假如某个进程在不停写数据,当写入大小触发dirty_background_ratio_10%时,脏页开始写入磁盘,写入数据大小触发dirty_ratio_20%时(磁盘IO速度远比写内存慢),如果继续写,会被内核阻塞,等脏页部分被写入磁盘,释放pagecached后,进程才能继续写入内存;
参考:
https://www.cnblogs.com/ywcz060/p/5589926.html
https://blog.csdn.net/u010039418/article/details/107500892
内存回收,是通过kswap进行的,涉及三个水位值,min\low\high:
:~$ sudo cat /proc/zoneinfo
Node 0, zone DMA
...
Node 0, zone Normal
pages free 201343
min 15777
low 23415
high 31053
spanned 7790592
当内存水位低于low时开始回收,直到水位达到high停止。当水位低于min时,直接阻塞应用,在进程上下文直接回收。
默认值60,最大值100.Swappiness越大,越优先回收匿名页;
即使Swappiness设置为0,优先回收文件背景页,以使达到最低水位;假使达不到,还是会回收匿名页;
./swapoff –a
echo 1 \> /proc/sys/vm/overcommit_memory //
cd /sys/fs/cgroup/memory
mkdir A
cd A
sudo echo \$(100\*1024\*1024) \> memory.limit_in_bytes //限制最大内存100M
//a.out放到A cgroup执行;
sudo cgexec –g memory:A ./a.out
swap主要是解决内存不足的问题。
目前, Hadoop集群,kubernetes等都会关闭swap。首先是swap交换引起IO和内存的性能问题。另外,开启swap后通过cgroups设置的内存上限就会失效。
另外,对于cgroup而言,设置swappiness=0,该group的swap回收会被关闭;但是全局的swap还是可以回收;
Q: swap只针对匿名页,不会对文件页进行交换。比如lib.so等,没有意义。
A:是的,文件页是直接回收,匿名页才会被交换到swap分区。