Linux OOM
OOM是什么
为什么会发生 OOM
OOM(Out of Memory)错误发生在系统内存耗尽时,无法为新的进程或现有进程分配足够的内存。具体原因包括:
-
内存泄漏:程序在运行过程中申请了内存但没有正确释放,导致内存逐渐耗尽。
-
内存使用过大:某些程序或进程在某一时刻申请了过多的内存,超过了系统的实际可用内存资源。
-
共享内存和缓存:系统中的其他进程或服务占用了大量的内存资源,导致当前进程无法获得足够的内存空间。
-
内核内存管理:操作系统内核在管理内存时,也会占用一定的内存资源。当内核内存管理不当或内核模块占用过多内存时,也会导致内存不足。
OOM 的条件
Linux 系统中的 OOM Killer 机制会在系统内存耗尽时启动,以确保系统继续运行。OOM Killer 的触发条件包括:
-
内存分配失败:当系统尝试分配内存但失败时,会触发 OOM Killer。
-
内存压力:系统检测到内存压力过大,无法满足当前需求时,会触发 OOM Killer。
-
内存释放尝试失败:系统尝试通过清理缓存、交换内存等方式释放内存,但未能成功时,会触发 OOM Killer.
OOM Killer 的工作机制
-
评分机制:系统会为每个进程分配一个
oom_score
值,基于进程占用的内存量、运行时长以及是否设置了特定的保护标志等因素计算得出。当内存紧张时,评分最高的进程最先被 OOM Killer 终止. -
选择目标进程:在获得所有运行进程的评分后,OOM Killer 会选择
oom_score
最高的进程作为终止目标。某些特殊进程可能会设置负的oom_adj
,以降低被 OOM Killer 终止的可能性.
OOM 分类
-
JVM OOM:主要发生在Java虚拟机内部,通常与Java应用程序的内存管理有关。解决方法通常涉及调整JVM参数、优化代码或增加内存。
-
系统 OOM:发生在操作系统层面,通常与系统资源管理有关。解决方法通常涉及增加物理内存、调整系统配置或优化系统资源使用
系统OOM时内存分类
在 Linux 系统中,内存分类可以分为以下几种:
-
堆内存(Heap Memory):
-
堆内存是动态分配的内存,用于存储程序运行时动态分配的对象和数据结构。
-
通过
malloc
、calloc
、realloc
等函数进行分配,通过free
函数进行释放。 -
堆内存的大小可以在程序运行时动态调整。
-
-
栈内存(Stack Memory):
-
栈内存用于存储函数调用时的局部变量、函数参数和返回地址。
-
栈内存是自动分配和释放的,当函数调用时分配内存,函数返回时释放内存。
-
栈内存的大小通常是固定的,由操作系统分配。
-
-
共享内存(Shared Memory):
-
共享内存是一种进程间通信(IPC)机制,允许多个进程共享同一块内存区域。
-
通过
shmget
、shmat
、shmdt
等系统调用进行分配和管理。 -
共享内存可以用于高效的数据交换,因为它避免了数据的复制。
-
-
内核内存(Kernel Memory):
-
内核内存用于存储操作系统内核和内核模块的数据结构和代码。
-
内核内存是由操作系统管理的,用户进程无法直接访问。
-
内核内存包括内核堆、内核栈和内核缓存等。
-
-
缓存和缓冲区(Cache and Buffers):
-
缓存用于存储频繁访问的数据,以提高访问速度。
-
缓冲区用于临时存储数据,以便在数据传输过程中进行处理。
-
缓存和缓冲区由操作系统自动管理,用户无需手动干预。
-
Java服务运行中产生的额外内存
Java服务在运行中除了堆内内存, 还会产生以下堆外内存
堆外内存(Off-Heap Memory)
堆外内存是指不由 JVM 管理的内存区域,通常通过直接内存(Direct Memory)或本地内存(Native Memory)进行分配。堆外内存包括以下部分:
-
直接内存(Direct Memory):通过
ByteBuffer.allocateDirect()
方法分配的内存,用于高效的 I/O 操作。 -
本地方法栈(Native Method Stack):用于存储本地方法调用的局部变量和参数,JNI 调用或者第三方库(如 Netty、FFmpeg)可能分配内存而未释放。
-
共享内存(Shared Memory):用于进程间通信(IPC),允许多个进程共享同一块内存区域。
-
线程的栈内存: 每个线程分配栈内存,线程数量过多会快速耗尽内存。
-
系统内核内存: Socket 缓冲区、文件句柄、TCP 缓冲区等,都可能由内核维护并消耗内存
-
动态加载的类: 不断加载类,元空间会增加
-
内存映射文件 (Memory-Mapped Files): JVM 或底层库(如 NIO 文件通道)可能使用内存映射文件将磁盘内容直接映射到内存, 通过 mmap 系统调用分配, 不占用堆内内存消耗系统内存