前言
先来看下本机系统内存使用情况:数值默认单位为 KB
# 笔记本本机 16G 内存,Ubuntu 18
$ free
total used free shared buff/cache available
Mem: 16306984 13655780 1205560 576992 1445644 1748628
Swap: 2097148 1620224 476924
# -h 适配可读的单位。-m 即 MB 为单位
$ free -h
total used free shared buff/cache available
Mem: 15G 13G 320M 649M 1.6G 1.0G
Swap: 2.0G 1.6G 435M
Total
(全部) : 总内存大小- Used(已用) : 已使用的内存大小,包含共享内存
- Free(未用) : 未使用内存的大小
- Shared(共享): 共享内存的大小
Buff/Cache
(缓存):块设备缓存区 和 文件缓存大小Available
(可用) : 新进程可用内存的大小(未使用内存 + 可回收的内存)Swap
(交换区) :把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。
关于 Buff/Cache
,可以通过 man free
手册来查看说明:
-
Buffers
:是内核缓冲区用到的内存,对应的是/proc/meminfo
中的Buffers
值。 -
Cache
: 是Page Cache
页缓存 和Slab
用到的内存,对应的是/proc/meminfo
中的Cached
与SReclaimable
之和。Page Cache
页缓存:缓存从文件读取的数据。Slab
:用于记录可回收部分 和 不可回收部分。
由上,并结合平时开发经验,推测内存组成部分:堆内存、堆外内存、Page Cache
、其他
问题:在 64位系统下,虚拟地址空间中 内核空间 和 用户空间 均占 128T,可对应的空间在物理内存中占多少?
这其实没有一个明确的答案,因为 内核空间 和 用户空间 共享同一个物理内存。 但我们平时开发时(
Kafka
、ES
)经常接触到Page Cache
,同时希望能分配更多内存给Page Cache
。 要么限制Page Cache
的大小,要么限制应用程序的大小。 一般我们都选择限制应用程序,举个栗子: 安装ES
在 8C16G 机子下,设置最大和最小堆内存为 8G,-Xms8g -Xmx8g
。目的就是留一半内存给Page Cache
。
下面着重来介绍 Page Cache
与 Swap
。
Page Cache
页缓存
Page cache
:也叫页缓冲 或 文件缓冲,也叫 ES
中的 filesystem cache
。
问题:为什么需要 Page Cache
?
- 回答:加速对文件内容的访问。减少磁盘
I/O
,提升应用的I/O
速度。
举个栗子:写文件
调用
write(2)
函数,写入Page Cache
后就返回,内核会自动刷盘(把Page Cache
中数据写入磁盘)。
来看张神图,应用程序产生 Page Cache
的逻辑示意图:
从图中可知:
Page Cache
是内核管理的内存,属于内核不属于用户。- 有两种方式可以产生
Page Cache
:标准I/O
(Buffered I/O
) 和MMAP
(Memory-Mapped I/O
) Linux I/O
有 3 种方式:标准I/O
、MMAP
和Direct I/O
要分析此图,我们还需要先了解 Linux IO
栈:
- 虚拟文件系统(
VFS
) :对各种文件系统的一个抽象,它为各种文件系统提供了一个通用的接口。 - 块层(
Block I/O
) :管理块设备的IO
队列,对IO
请求进行合并、排序。 - 设备驱动(
Device Driver
) :操作存储介质。
再看 Linux I/O
3 种方式,这里做了简化:
问题:为什么上张图里有
VFS
,这图改为File System
文件系统? 回答:VFS
就像接口,定义好接口,其他人就来实现就行。File System
文件系统更为具体化。 举个栗子:open()
函数,可以以特定的文件描述符打开某一个文件。 使用:fd = open("myfile", O_RDWR | O_CREAT | O_SYNC | O_DIRECT, S_IRUSR | S_IWUSR);
O_DIRECT
:无缓冲的输入、输出。对应Direct I/O
。O_SYNC
:以同步IO
方式打开文件。
这里着重再介绍下 Direct I/O
: 是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。
想要实现直接
I/O
,需要在系统调用中,指定O_DIRECT
标识。
官方文档中的说明,主要体现了 3 点:
- 缺点: 这种方式会降低性能。(磁盘读写、磁盘寻道等。)
- 特殊应用场景: 应用程序自己管理缓存,文件
I/O
直接对接用户空间缓冲区。(对开发者能力要求高) - 不保证同步: 不能确保每次写入都同步数据到磁盘,还是需要设置
O_SYNC
标识或者手动fsync
。
Tips
:Java
基本库不支持 Direct I/O
,若想使用需引入 JNA
包。
Swap
交换区
Swap
说白了就是把一块磁盘空间或者一个本地文件(以下讲解以磁盘为例),当成内存来使用。
Swap
把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
- 换出:就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
- 换入:则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。
问题:为什么一些中间件都建议关闭 Swap
?
-
性能问题: 例如
ES
,与JVM
的GC
相关。GC
会遍历所用到的堆的内存,如果有部门内存被Swap
到磁盘,那么GC
遍历时候就会去查磁盘,磁盘IO
又很慢,这就会导致应用程序STW
假死一段时间。 -
管理问题: 例如
K8s
,开启Swap
后通过Cgroups
设置的内存上限就会失效。