本文介绍了常用的内存查询命令和内存相关指标的含义。
Linux内存简介
dmesg | grep Memory
Memory: 131604168K/134217136K available (14346K kernel code, 9546K rwdata, 9084K rodata, 2660K init, 7556K bss, 2612708K reserved, 0K cma-reserved)
- free命令
free total used free shared buff/cached available Mem: 131641168 1827360 122430044 0 63308 3415776
- /proc/meminfo命令
cat /proc/meminfo MemTotal: 131641168 kB MemFree: 122430044 kB MemAvailable: 124968912 kB Buffers: 63308 kB Cached: 3415776 kB SwapCached: 0 kB Active: 613436 kB Inactive: 7674576 kB Active(anon): 3504 kB Inactive(anon): 4784612 kB Active(file): 609932 kB Inactive(file): 2889964 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 1472 kB Writeback: 0 kB AnonPages: 4641928 kB Mapped: 1346848 kB Shmem: 6972 kB KReclaimable: 174888 kB Slab: 352948 kB SReclaimable: 174888 kB SUnreclaim: 178060 kB KernelStack: 48416 kB PageTables: 30296 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 65820584 kB Committed_AS: 22967072 kB VmallocTotal: 34359738367 kB VmallocUsed: 77312 kB VmallocChunk: 0 kB Percpu: 42752 kB HardwareCorrupted: 0 kB AnonHugePages: 2852864 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB DupText: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 288568 kB DirectMap2M: 12294144 kB DirectMap1G: 123731968 kB
total = used + free + buff/cache //总内存=已使用内存+空闲内存+缓存
其中,已使用内存数据包括Kernel消耗的内存和所有进程消耗的内存。
进程内存
- 虚拟地址空间映射的物理内存。
- 读写磁盘生成PageCache消耗的内存。
虚拟地址映射的物理内存
- 物理内存:硬件安装的内存(内存条)。
- 虚拟内存:操作系统为程序运行提供的内存。程序运行空间包括用户空间(用户态)和内核空间(内核态)。
- 用户态:低特权运行程序。
数据存储空间包括:
- 栈(Stack):函数调用的函数栈
- MMap(Memory Mapping Segment):内存映射区
- 堆(Heap):动态分配内存
- BBS区:未初始化的静态变量存放区
- Data区:已初始化的静态常量存放区
- Text区:二进制可执行代码存放区
用户态中运行的程序通过MMap将虚拟地址映射至物理内存中。
- 内核态:运行的程序需要访问操作系统内核数据。数据存储空间包括:
- 直接映射区:通过简单映射将虚拟地址映射至物理内存中。
- VMALLOC:内核动态映射空间,用于将连续的虚拟地址映射至不连续的物理内存中。
- 持久内核映射区:将虚拟地址映射至物理内存的高端内存中。
- 固定映射区:用于满足特殊映射需求。
- 用户态:低特权运行程序。

PageCache

进程内存统计指标
单进程内存统计指标
- Anonymous(匿名页):程序自行使用的堆栈空间,在磁盘上没有对应文件。
- File-backed(文件页):资源存放在磁盘文件中,文件内包含代码段、字体信息等内容。
- anno_rss(RSan):所有类型资源的独占内存。
- file_rss(RSfd):File-backed资源占用的所有内存。
- shmem_rss(RSsh):Anonymous资源的共享内存。
指标查询命令如下:
命令 | 查询指标 | 说明 | 计算公式 |
---|---|---|---|
| VIRT | 虚拟地址空间。 | 无 |
RES | RSS映射的物理内存。 | anno_rss + file_rss + shmem_rss | |
SHR | 共享内存。 | file_rss + shmem_rss | |
MEM% | 内存使用率。 | RES / MemTotal | |
| VSZ | 虚拟地址空间。 | 无 |
RSS | RSS映射的物理内存。 | anno_rss + file_rss + shmem_rss | |
MEM% | 内存使用率。 | RSS / MemTotal | |
| USS | 独占内存。 | anno_rss |
PSS | 按比例分配内存。 | anno_rss + file_rss/m + shmem_rss/n | |
RSS | RSS映射的物理内存。 | anno_rss + file_rss + shmem_rss |

进程控制组内存统计指标
控制组(Cgroup)用于对Linux的一组进程资源进行限制、管理和隔离。更多信息,请参见官方文档。

cgroup.event_control #用于eventfd的接口
memory.usage_in_bytes #显示当前已用的内存
memory.limit_in_bytes #设置/显示当前限制的内存额度
memory.failcnt #显示内存使用量达到限制值的次数
memory.max_usage_in_bytes #历史内存最大使用量
memory.soft_limit_in_bytes #设置/显示当前限制的内存软额度
memory.stat #显示当前cgroup的内存使用情况
memory.use_hierarchy #设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面
memory.force_empty #触发系统立即尽可能的回收当前cgroup中可以回收的内存
memory.pressure_level #设置内存压力的通知事件,配合cgroup.event_control一起使用
memory.swappiness #设置和显示当前的swappiness
memory.move_charge_at_immigrate #设置当进程移动到其他cgroup中时,它所占用的内存是否也随着移动过去
memory.oom_control #设置/显示oom controls相关的配置
memory.numa_stat #显示numa相关的内存
- memory.limit_in_bytes:限制当前控制组可以使用的内存大小。对应K8s、Docker下memory limit指标。
- memory.usage_in_bytes:当前控制组里所有进程实际使用的内存总和,约等于memory.stat文件下的RSS+Cache指标值。
- memory.stat:当前控制组的内存统计详情。
memory.stat文件字段 说明 cache PageCache缓存页大小。 rss 控制组中所有进程的anno_rss内存之和。 mapped_file 控制组中所有进程的file_rss和shmem_rss内存之和。 active_anon 活跃LRU(least-recently-used,最近最少使用)列表中所有Anonymous进程使用内存和Swap缓存,包括 tmpfs
(shmem
),单位为字节。inactive_anon 不活跃LRU列表中所有Anonymous进程使用内存和Swap缓存,包括 tmpfs
(shmem
),单位为字节。active_file 活跃LRU列表中所有File-backed进程使用内存,以字节为单位。 inactive_file 不活跃LRU列表中所有File-backed进程使用内存,以字节为单位。 unevictable 无法再生的内存,以字节为单位。 以上指标中如果带有
total_
前缀则表示当前控制组及其下所有子孙控制组对应指标之和。例如total_rss指标表示当前控制组及其下所有子孙控制组的RSS指标之和。
总结
指标 | 单进程 | 进程控制组(memcg) |
---|---|---|
RSS | anon_rss + file_rss + shmem_rss | anon_rss |
mapped_file | 无 | file_rss + shmem_rss |
cache | 无 | PageCache |
- 控制组的RSS指标只包含anno_rss,对应单进程下的USS指标,因此控制组的mapped_file+RSS则对应单进程下的RSS指标。
- 单进程中PageCache需单独统计,控制组中memcg文件统计的内存已包含PageCache。
Docker和K8s中的内存统计
Docker和K8s中的内存统计即Linux memcg进程统计,但两者内存使用率的定义不同。
docker stat命令

func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
return float64(mem.Usage - mem.Stats["cache"])
}
- LIMIT对应控制组的memory.limit_in_bytes。
- MEM USAGE对应控制组的memory.usage_in_bytes - memory.stat[total_cache]。
kubectl top pod命令
kubectl top
命令通过Metric-server和Heapster获取Cadvisor中working_set的值,表示Pod实例使用的内存大小(不包括Pause容器)。Petrics-server中Pod内存获取原理如下,更多信息,请参见官方文档。func decodeMemory(target *resource.Quantity, memStats *stats.MemoryStats) error {
if memStats == nil || memStats.WorkingSetBytes == nil {
return fmt.Errorf("missing memory usage metric")
}
*target = *uint64Quantity(*memStats.WorkingSetBytes, 0)
target.Format = resource.BinarySI
return nil
}
func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
if s.MemoryStats.UseHierarchy {
ret.Memory.Cache = s.MemoryStats.Stats["total_cache"]
ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]
ret.Memory.Swap = s.MemoryStats.Stats["total_swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"]
} else {
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
ret.Memory.Swap = s.MemoryStats.Stats["swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"]
}
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
}
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
ret.Memory.ContainerData.Pgmajfault = v
ret.Memory.HierarchicalData.Pgmajfault = v
}
workingSet := ret.Memory.Usage
if v, ok := s.MemoryStats.Stats["total_inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
ret.Memory.WorkingSet = workingSet
}
通过以上命令算法可以得出,kubectl top pod
命令查询到的Memory Usage = Memory WorkingSet = memory.usage_in_bytes - memory.stat[total_inactive_file]。
总结
命令 | 生态 | Memory Usage计算方式 |
---|---|---|
| Docker | memory.usage_in_bytes - memory.stat[total_cache] |
| K8s | memory.usage_in_bytes - memory.stat[total_inactive_file] |
进程组生态 | 计算公式 |
---|---|
Memcg | rss + cache(active cache + inactive cache) |
Docker | rss |
K8s | rss + active cache |
Java内存统计
Java进程的虚拟地址空间

通过JMX获取内存指标


JMX暴露的指标并未展示JVM进程全部的内存指标,例如,Java Thread消耗的内存就未展示。因此,JMX暴露的内存Usage数据累加得出的结果并不等于JVM进程的RSS值。
JMX MemoryUsage工具

其中,used为物理内存消耗。
NMT工具
Java Hotspot VM提供了一个跟踪内存的工具Native Memory Tracking (NMT),具体使用方法,请参见官方文档。
jcmd 7 VM.native_memory
Native Memory Tracking:
Total: reserved=5948141KB, committed=4674781KB
- Java Heap (reserved=4194304KB, committed=4194304KB)
(mmap: reserved=4194304KB, committed=4194304KB)
- Class (reserved=1139893KB, committed=104885KB)
(classes #21183)
( instance classes #20113, array classes #1070)
(malloc=5301KB #81169)
(mmap: reserved=1134592KB, committed=99584KB)
( Metadata: )
( reserved=86016KB, committed=84992KB)
( used=80663KB)
( free=4329KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=1048576KB, committed=14592KB)
( used=12806KB)
( free=1786KB)
( waste=0KB =0.00%)
- Thread (reserved=228211KB, committed=36879KB)
(thread #221)
(stack: reserved=227148KB, committed=35816KB)
(malloc=803KB #1327)
(arena=260KB #443)
- Code (reserved=49597KB, committed=2577KB)
(malloc=61KB #800)
(mmap: reserved=49536KB, committed=2516KB)
- GC (reserved=206786KB, committed=206786KB)
(malloc=18094KB #16888)
(mmap: reserved=188692KB, committed=188692KB)
- Compiler (reserved=1KB, committed=1KB)
(malloc=1KB #20)
- Internal (reserved=45418KB, committed=45418KB)
(malloc=45386KB #30497)
(mmap: reserved=32KB, committed=32KB)
- Other (reserved=30498KB, committed=30498KB)
(malloc=30498KB #234)
- Symbol (reserved=19265KB, committed=19265KB)
(malloc=16796KB #212667)
(arena=2469KB #1)
- Native Memory Tracking (reserved=5602KB, committed=5602KB)
(malloc=55KB #747)
(tracking overhead=5546KB)
- Shared class space (reserved=10836KB, committed=10836KB)
(mmap: reserved=10836KB, committed=10836KB)
- Arena Chunk (reserved=169KB, committed=169KB)
(malloc=169KB)
- Tracing (reserved=16642KB, committed=16642KB)
(malloc=16642KB #2270)
- Logging (reserved=7KB, committed=7KB)
(malloc=7KB #267)
- Arguments (reserved=19KB, committed=19KB)
(malloc=19KB #514)
- Module (reserved=463KB, committed=463KB)
(malloc=463KB #3527)
- Synchronizer (reserved=423KB, committed=423KB)
(malloc=423KB #3525)
- Safepoint (reserved=8KB, committed=8KB)
(mmap: reserved=8KB, committed=8KB)
Reserved和Committed指标
NMT的输出统计透出了两个概念:Reserved和Committed。但Reserved和Committed都无法映射已使用物理内存(Used)。

总结
- 常见Java应用工具统计出来的指标主要是由JMX暴露的,关于内存的统计,JMX暴露了一些JVM内部可跟踪的MemoryPool,这些MemoryPool的总和并不能和JVM进程的RSS映射。
- NMT暴露了JVM内部使用内存的细节,但是衡量结果并不是Used,而是Committed。因此,总Committed应该比RSS稍大。
- NMT对JVM外的一些内存无法跟踪。如果Java程序有额外的Molloc等行为,NMT是统计不到的,因此如果看到RSS比NMT大,也是正常的。
- 如果NMT的Committed和RSS相差非常多,那就要怀疑内存是否泄漏。
您可以通过NMT的其他指标做进一步排查:
- 借助NMT的baseline和diff排查是JVM内部哪个区域出现问题。
- 借助NMT结合pmap排查JVM之外的内存问题。
内存常见问题
为什么ARMS应用监控产品界面上看到的Heap、Non-heap等内存总和与通过TOP看到的RES相差很多?
答:ARMS界面上的数据来源来自JMX。从NMT输出得出:Heap、Non-heap并不是进程的全部内存消耗,相差的内容至少包含Thread、GC、Symbol等部分,而且还缺少不能被JVM跟踪的内存部分。
为什么ARMS应用监控产品界面上看到的Heap、Non-heap等内存总和与在Prometheus、Grafana中看到的内存使用率相差很多?
答:ARMS界面上的数据来源来自JMX。
Grafana上看到的内存使用率是通过Prometheus Query Language查询的指标,一般是Pod,Container对应的名为container_memory_working_set_bytes的指标,和上文提到的
kubectl top pod
命令查询到的一致,即统计的是memcg(进程组)的rss + actived cache。Pod发现一些OOM Killer导致的重启,如何通过ARMS应用监控排查?
答:只依靠应用监控无法完全定位。关于Java进程,应用监控能拿到的数据只有JMX暴露出来的这部分,而JVM进程的RSS消耗不一定是JMX暴露的出来的。即便借助K8s的Prometheus监控生态,中间还是有很多信息需要确认。
例如:- Pod内是单进程模型吗?
用于排查其他进程内存消耗的干扰。
- JVM进程外是否存在泄漏?
例如glibc导致的内存泄漏。
如果是简单的Heap、Class、DirectorMemory的容量规划问题,则比较容易排查。
- Pod内是单进程模型吗?