Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。Linux的空间又分为内核空间和用户空间,在32位中,内核空间占1G,用户空间占3G;而在64位中,内核空间和用户空间各占128T。如图3-24所示。
图3-24 Linux内存空间
Linux不会直接访问物理内存地址,而是访问虚拟地址。内存映射,就是将虚拟内存地址映射到物理内存地址。而当进程访问的虚拟地址在页表中查不到的时候,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。内核映射如图3-25所示。
图3-25 内核映射
虚拟内存空间分布以32位为例,如图3-26所示。
图3-26 虚拟内存空间分布(以32位为例)
从下到上依次为只读段、数据段、堆、文件映射和栈五部分组成。
•栈。
包括局部变量和函数调用的上下文等。栈的大小是固定的,一般为8MB。
•文件映射段。
包括动态分配的内存,从低地址开始向上增长。
•堆。
包括动态分配的内存,从低地址开始向上增长。
•数据段。
数据段。
•只读段。
包括代码和常量。
堆和文件映射段的内存是动态分配的,可以使用C标准库的malloc()或者mmap()方法进行分配。
[33]malloc()是C语言提供的内存分配方法,有两种实现方式:brk()和mmap()。
对小块内存是指≤128K的内存,C语言使用 brk()函数来分配,它通过移动堆顶的位置来分配内存。这些内存释放后并不会马上还给系统,而是被缓存起来,就可以重复使用了。brk()函数的缓存,可以减少缺页异常的发生,从而提高内存访问的效率。但是由于这些内存没有还给系统,在内存工作繁忙的时候,频繁的内存分配和释放会造成大量的内存碎片。
大块内存是指>128K的内存,使用内存映射mmap()函数来分配,它在文件映射段找一块空闲内存分配出去。mmap()方式分配的内存,可以在释放时直接归还系统,所以每次 mmap都会发生缺页异常。在内存工作繁忙的时候,频繁的内存分配会导致大量的缺页异常,使内核的管理工作加大。这也正是malloc仅对大块内存使用mmap的原因。
对于回收,有以下几种策略。
•FIFO(First In First Out)。
先进先出算法,即先放入缓存的先被移除。
•LRU(Least Recently Used)。
最近最少使用算法,离现在时间最久的先被移除。
•LFU(Least Frequently Used)。
最不常用算法,一定时间段内使用【次数(频率)】最少的先被移除。
Linux的内存回收一般分为以下三种方式。
•使用LRU(Least Recently Used)回收最近最少使用的缓存。
•通过LFU算法,把不常用的内存通过交换分区(Swap:把硬盘空间作为内存使用的机制)直接写到磁盘中。
•杀死进程,内存紧张时系统通过OOM(Out of Memory),直接杀掉占用大量内存的进程。
在这里简单介绍一下OOM。Linux内核根据应用程序的要求来分配内存,应用程序分配了内存可能没有实际全部使用,为了提高性能,这部分没用的内存可以留作其他用途,由于这部分内存是属于每个进程的,内核直接回收利用会带来麻烦,所以内核采用一种过度分配内存(over-commit memory)的机制来间接利用这部分“空闲”的内存,来提高整体内存的使用效率。一般来说这样是没有问题的,但是当大多数应用程序都消耗完自己的内存的时候就有问题了,因为这些应用程序的内存需求之和超出了物理内存(包括SWAP)的容量,内核必须杀掉一些进程才能产生空间来保障系统的正常运行。Linux内核的这种机制叫做OOMkiller(Out-Of-Memorykiller),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽内核会把该进程杀掉。在OOM中,使用oom_score来为每个进程的内存使用情况进行评分。
•一个进程消耗的内存越大,oom_score就越大。
•一个进程运行占用的CPU越多,oom_score 就越小。
当进程的oom_score 越大,表示消耗的内存越多,也就越容易被OOM杀死,从而可以更好保护系统。也可以通过oom_adj来调整oom_score。oom_adj的范围是[-17, 15],数值越大,表示进程越容易被OOM杀死;数值越小,表示进程越不容易被OOM杀死,其中-17表示禁止OOM。oom_adj位于/proc/$(pidof sshd)/oom_adj,可以通过
#echo -16 > /proc/$(pidof sshd)/oom_adj
来设置。也可以通过
# cat /proc/$(pidof sshd)/oom_adj
-16
来获得。
可以通过命令free来查看内存使用情况。
# free
total used free shared buff/cache available
Mem: 4312648 1577592 210964 18624 2524092 2426060
Swap: 969960 1036 968924
其参数参考表3-7所示。
表3-7 freet命令输出详解
标识 | 含义 |
---|---|
total | 总内存大小 |
used | 已使用内存的大小,包含了共享内存 |
free | 未使用内存的大小 |
shared | 共享内存的大小 |
buff/cache | 缓存和缓冲区的大小 |
available | 新进程可用内存的大小 |
注意:在这里available不仅包含未使用内存,还包括了可回收的缓存。查看内存瓶颈并非看free即可。
也可以用top命令的第4、5行及进程部分来查看内存的使用情况。
#top
…
KiB Mem : 4312648 total, 107164 free, 1584800 used, 2620684 buff/cache
KiB Swap: 969960 total, 968924 free, 1036 used. 2417764 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20864root 20 0 4784 1520 1236 R 98.0 0.0 0:03.34 gzip
2044 jerry 20 0 3745868 204216 79064 S 8.9 4.7 1:13.52 gnome-shell
20866root 20 0 14696 812 748 S 6.3 0.0 0:00.22 cpio
1915 jerry 20 0 462104 86288 47384S 4.6 2.0 0:26.55 Xorg
2416 jerry 20 0 730376 38900 27232 S 3.0 0.9 0:14.85 gnome-terminal-
22 root 20 0 0 0 0 S 2.6 0.0 0:00.50 ksoftirqd/2
top命令第4、5行其参数请参考表3-8。
表3-8 top命令中第4、5行参数含义
标识 | 解释 |
---|---|
Mem:###k total | 物理内存总量 |
Mem:###k used | 使用的物理内存总量 |
Mem: ###k free | 空闲内的存总量 |
Mem:###k buffers | 用作内核缓存的内存总量 |
Swap: ###k total | 交换区的总量 |
Swap: ###k used | 使用的交换区总量 |
Swap: ###k free | 空闲的交换区总量 |
Swap: ###k cached | 缓冲的交换区总量 |
top命令进程中关于内存性能如表3-9所示。
表3-9 top命令进程中关于内存性能参数
标记 | 解释 |
---|---|
VIRT | 进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。 |
RES | 常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。 |
SHR | 共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。 |
%MEM | 进程使用物理内存占系统总内存的百分比。 |