2154.OOM相关问题排查
PostgreSQL OOM最佳实践
晟数学苑2021-07-27
664
点击蓝字关注我们
OOM是Linux中一个比较常见的情况,PostgreSQL数据库触发OOM现象就是数据库进程被KILL了。OOM发生的原因有很多,这里我们从OOM的产生以及如何在PostgreSQL中预防OOM发生来进行研究。
OOM介绍
什么是OOM?
OOM(out-of-memory),顾名思义就是内存溢出了,之所以会出现这种情况和内存分配的overcommit有关。
Linux为了保证在有限的内存中尽可能多的运行更多的进程,在内存分配策略中提出了overcommit的策略,即允许内存溢出。之所以可以这么做是因为我们进程在申请内存时并不会立刻分配内存页,而是到使用时才进行分配。所以Linux才允许进程overcommit,因为即使申请了这么多实际可能也用不到,所以便允许进程申请了。
那么这么做有什么好处呢?对于一些内存敏感的应用来说,随时可能发生新的内存分配,而LINUX中有大量并发的进程,临时使用内存的总量变化很大。很多内存分配需求是临时性的,那么如果不凑巧,有时候要分配内存的时候,物理内存已经分配完了,虽然几毫秒后很可能就有一些内存可以归还,另外还有一些进程分配了内存后没有马上使用,但是如果不能内存超分配,这时候内存分配就会失败。而如果能够支持内存超分配,那么就可以利用这个时间差,有效的解决这个内存短期不足的问题。
但是这么做便会导致内存不足的情况,当出现这种情况时,有两种选择,一种是直接panic系统,然后几秒钟后LINUX自动重启,这个行为可以通过kernel.panic_on_oom参数设置,不过我们一般不采用这种模式。另一种就是我们要说的OOM了,挑出某个进程kill掉来释放内存。
我们可以通过内核参数 vm.overcommit _ memory 设置是否使用 memory overcommit。
0 – Heuristic overcommit handling.
这是缺省值,它允许overcommit,但过于明目张胆的overcommit会被拒绝,比如malloc一次性申请的内存大小就超过了系统总内存。Heuristic的意思是“试探式的”,内核利用某种算法猜测你的内存申请是否合理,它认为不合理就会拒绝overcommit。
1 – Always overcommit. 允许overcommit,对内存申请来者不拒。
2 – Don’t overcommit. 禁止overcommit。
OOM流程
OOM kill的流程导致如下:
除此之外,为了防止某些情况进程被误kill,将会进行以下检查:
是否有足够的交换空间 ( nr _ swap _ pages 0 ) ?如果是,不进行OOM;
自上次失败以来是否已经超过 5 秒?如果是,不进行OOM;
是在最后一秒内失败了吗?如果不是,不进行OOM;
如果至少在过去 5 秒内没有发生 10 次失败,就不进行 OOM;
是否有进程在过去 5 秒内被杀死?如果是,不进行OOM
如何选择进程进行OOM?
函数 select_bad_process() 负责选择要杀死的进程。它通过逐步执行每个正在运行的任务并计算它是否适合使用函数 badness() 进行终止来决定。Badness 计算如下:
badness_for_task = total_vm_for_task / (sqrt(cpu_time_in_seconds) *
sqrt(sqrt(cpu_time_in_minutes)))
上面这个公式简单来说就是倾向于kill占用内存大执行时间短的进程。
我们可以通过设置进程的oom_score来降低被oom kill的优先级。
oom_score在新版本的Linux内核中计算起来十分简单,就是这个进程占用的(物理内存+SWAP)的总和乘以10,也就是说某个进程的最大oom_score=1000,最小oom_score=0。oom killer总是根据这个值从高到低排序来查找可以杀死的进程,这样确保杀死进程后,可以释放最多的内存。
如果我们把某个进程的oom_score_adj设置为-1000(早期版本的linux oom_score的计算方法不同,因此调整adj的方法略有不同),那么这个进程的oom_score就会永远为0,也就是说这个进程永远不会被oom killer杀死。这也是我们保护某个进程,不让oom killer杀掉的主要方法。
PostgreSQL避免OOM发生方法
操作系统层:
如前面所述,主要设置以下两点:
vm.overcommit_memory设置为2:
sysctl -w vm.overcommit_memory=2
修改postgres进程的oom_score:
echo -1000 > /proc/self/oom_score_adj
数据库层:
数据库中可以进行以下调整来降低OOM发生的可能:
分区表特别多, 需要注意分区的catalog cache占用大量内存(PG12之后基本可以避免,或者使用pg_pathman)
避免长连接,连接时间越长, 访问的元数据积累越多, 导致每个会话的私有内存较大。可以通过降低总连接数或者使用连接池
使用HUGEPAGE, 避免page table占用较大内存. 最终引起OOM
避免使用较大的work_mem或hash_mem_multiplier,优化SQL,减少hash agg、hash join的使用
诸如此类的还有很多,总的来说就是降低数据库进程的内存使用。
参考链接:
https://github.com/digoal/blog/blob/master/202104/20210415_04.md
https://www.kernel.org/doc/gorman/html/understand/understand016.html
Linux之OOM简述
原创何权林2020-05-19
7347
环境:SUSE Linux Enterprise Server11
前言:
在我们维护Linux操作系统的时候,有时会遇到应用被无辜被Kill掉,查看系统日志会发现类似如下信息,
…host_name kernel:[…] app_name invoked oom-killer:…
一、什么是OOM?
OOM:out of memory,内存溢出。
oom_killer是Linux自我保护的方式,防止内存不足时出现严重问题。
Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(),挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。
二、查看是否发生OOM?
1、grep “oom-killer” /var/log/messages
2、dmesg | grep -i “memory”
注:看到硬件的检测或者断开连接的信息。
三、如何禁用OOM?
方式一:禁止某个进程被oom调用
echo -17 > /proc/pid/oom_adj,-17表示禁止这个进程被OOM掉.
方式二:设置整个系统(内存不足时是否调用OOM)
sysctl -w vm.panic_on_oom=1 (默认为0)
sysctl -p
0,表示内存不足时,执行OOM;(建议值)
1,表示内存不足时,部分情况执行OOM,部分情况直接重启系统;
2,表示内存不足时,直接重启系统。
注:也就是说内存不足,除了OOM就是重启。。。
方式三:系统是否可过量使用内存
参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略
当overcommit_memory=0,允许进程轻微过量使用内存,但对于大量过载请求则不允许(默认);
当overcommit_memory=1,永远允许进程overcommit;
当overcommit_memory=2,永远禁止overcommit。
注:设置为2后,系统内存可使用量减少,但可调节。如果内存不能超用了,也就不会触发OOM。
四、OOM时,依据什么杀掉进程?
参数:panic_on_oom: 用来控制当内存不足时是否启用OOM
值为0:内存不足时,启动 OOM killer。
值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。
值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。
参数:oom_kill_allocating_task: 用来决定杀掉哪种进程
值为0:会 kill 掉得分最高的进程。
值为非0:会kill 掉当前申请内存而触发OOM的进程。
当然,一些系统进程(如init)或者被用户设置了oom_score_adj的进程等可不是说杀就杀的。
参数:oom_dump_tasks:用来记录日志
oom_dump_tasks参数可以记录进程标识信息、该进程使用的虚拟内存总量、物理内存、进程的页表信息等。
值为0:关闭打印上述日志。在大型系统中,可能存在上千进程,逐一打印使用内存信息可能会造成性能问题。
值为非0:有三种情况会打印进程内存使用情况。
1、由 OOM 导致 kernel panic 时;
2、没有找到符合条件的进程 kill 时;
3、找到符合条件的进程并 kill 时。
参数:oom_adj、oom_score_adj 和 oom_score:用来控制进程打分(分数越高,就先杀谁)
这三个参数的关联性比较紧密,都和具体的进程相关,位置都是在 /proc/进程PID/ 目录下。
内核会对进程打分(oom_score),主要包括两部分,系统打分和用户打分。系统打分就是根据进程的物理内存消耗量;用户打分就是 oom_score_adj 的值。如果用户指定 oom_score_adj 的值为 -1000,也就是表示禁止 OOM killer 杀死该进程。
用户可以通过调整 oom_score_adj 的值来决定最终 oom_score 的值,oom_score_adj 的取值范围是 -1000~1000,为0时表示用户不调整 oom_score。另外,root进程拥有3%的内存使用特权,因此做最终 oom_score 计算时需要减去这些内存使用量。
oom_adj是一个旧的接口参数,其功能类似oom_score_adj,为了兼容,目前仍然保留这个参数,当操作这个参数的时候,kernel实际上是会换算成oom_score_adj。
五、总结
建议不要关闭OOM,如果系统中是多应用,建议找出重要的进程,单独设定禁用OOM。如果只有一个重要应用,建议从应用层面降低内存消耗。有时因为内存泄漏导致内存溢出,这时只能通过重启应用或者操作系统解决。
其他:
1、子进程会继承父进程的oom_adj。
2、OOM不适合于解决内存泄漏(Memory leak)的问题。
3、有时free查看还有充足的内存,但还是会触发OOM,是因为该进程可能占用了特殊的内存地址空间。
linux之如何利用oom-killer机制,排查内存飙升问题
PostgreSQL运维技术2023-07-21
671
服务器内存飙升,可能的排查步骤
服务器内存突然飙升可能是由许多因素引起的,以下是一些可能的排查步骤:
查看系统日志:查看系统日志可以帮助我们确定是否有任何错误或异常情况发生,这些情况可能导致内存使用率增加。
查看进程列表和资源占用情况:使用命令行工具(如top、htop或ps等)查看当前运行的进程列表和它们使用的资源情况,特别是内存占用情况。
使用性能监控工具:使用性能监控工具(如sar、vmstat、iostat等)来查看系统的整体性能,包括CPU、内存、磁盘和网络使用情况,以帮助我们确定系统中的瓶颈。
检查程序和服务日志:如果有正在运行特定的程序或服务,查看它们的日志可以帮助您确定是否有任何错误或异常情况发生,这些情况可能导致内存使用率增加。
检查数据库和缓存:如果有正在运行数据库或缓存服务,查看它们的日志和统计数据可以帮助您确定是否有任何查询或缓存问题导致内存使用率增加。
检查网络流量:如果服务器处理大量网络流量,考虑使用网络监控工具(如tcpdump或Wireshark等)来查看网络流量,以帮助我们确定是否有任何异常情况导致内存使用率增加。
总之,排查服务器内存突然飙升的原因需要综合考虑多个方面,需要根据实际情况采取不同的措施。
已经过去的故障如何排查
对于历史时刻出现的服务器内存飙升的问题,我们可以通过分析系统日志,查看系统日志以确定飙升发生的时间点,并查找与内存相关的任何错误或异常情况。
例如,我们可以查看系统日志中的oom-killer日志,该日志记录了Linux内核如何通过杀死进程来解决内存不足的问题。
在CentOS中,系统日志通常存储在/var/log目录下。以下是/var/log目录中一些常见的系统日志文件:
/var/log/messages:这是一个常见的系统日志文件,包括内核、系统服务和其他应用程序的日志信息。
/var/log/secure:这是一个安全日志文件,包括用户登录、sudo命令、ssh连接和其他安全相关的事件。
/var/log/boot.log:这是一个引导日志文件,包括系统引导过程中的信息。
/var/log/cron:这是一个cron任务日志文件,包括cron任务的执行记录。
/var/log/maillog:这是一个邮件服务器日志文件,包括邮件服务器的发送和接收记录。
oom-killer
oom-killer是Linux系统中的一个内核机制,用于在系统内存不足的情况下,杀死一些进程以释放内存。
oom-killer的全称是Out-Of-Memory Killer,即内存不足时的杀手。当系统内存不足时,Linux内核会尝试通过回收一些不必要的内存来释放资源,但如果无法释放足够的内存,就会触发oom-killer机制,强制杀死一些进程以释放内存。
打开系统日志文件,查看oom-killer相关信息。以下是笔者最近处理过的一个例子,以供参考。
1.首先打开/var/log/messages文件,查找oom-killer关键字,开始的信息是这样的。
Jul 19 07:10:22 GG-ops-yearning kernel: systemd invoked oom-killer: gfp_mask=0x3000d0, order=2, oom_score_adj=0
Jul 19 07:10:23 GG-ops-yearning kernel: systemd cpuset=/ mems_allowed=0
Jul 19 07:10:23 GG-ops-yearning kernel: CPU: 0 PID: 1 Comm: systemd Tainted: G OE ------------ T 3.10.0-1160.11.1.el7.x86_64 #1
Jul 19 07:10:23 GG-ops-yearning kernel: Hardware name: Alibaba Cloud Alibaba Cloud ECS, BIOS 449e491 04/01/2014
Jul 19 07:10:23 GG-ops-yearning kernel: Call Trace:
Jul 19 07:10:23 GG-ops-yearning kernel: [
…
上面的信息,记录了Linux系统中一个进程被oom-killer机制杀死的过程。
以下是日志的详细解释:
第一行日志记录了系统服务管理器systemd在某个时间点(2023年7月19日上午7点10分22秒)调用oom-killer机制杀死了一个进程。具体的oom-killer参数包括gfp_mask、order和oom_score_adj。
第二行日志记录了被杀死进程的启动参数,包括cpuset和mems_allowed。
接下来的几行日志记录了一些与进程相关的信息,如CPU ID、进程ID、进程名称和内核版本等。
最后几行日志记录了一些内核调用栈信息,这些信息通常用于分析和解决系统问题。
oom_score_adj
上面我们提到当服务器内存不足时,oom会杀死一些进程来释放内存,但是哪些进程会被选择呢?答案就跟oom_score_adj有关。
其实 Linux 的每个进程都有一个 oom_score (位于/proc//oom_score),这个值越大,就越有可能被 OOM Killer 选中。oom_score 和 oom_score_adj (OOM score adjustment)是 Linux 内核中用于控制 OOM killer 行为的两个参数。
oom_score 是每个进程的一个只读整数值,表示该进程在内存不足时被 OOM killer 杀死的优先级。oom_score 是根据进程的 oom_score_adj 值计算出来的,该值可以通过 proc/[pid]/oom_score_adj 文件进行设置。oom_score 越高,进程越容易被杀死。
我们可以调整oom_score_adj的值来防止服务器上比较重要的应用被kill,比如例如 核心的应用进程,我们想尽量避免它被 OOM Killer 杀死,这时候就可以调低它的 oom_score_adj 的值。
cpuset && mems_allowed
"systemd cpuset=/"和"mems_allowed=0"是由systemd进程设置的cgroup参数,用于限制该进程的资源使用。
"systemd cpuset=/"表示该进程被分配到了默认的cgroup中,并且没有任何CPU资源限制。也就是说,该进程可以使用系统中的所有CPU核心。
"mems_allowed=0"指定该进程只能使用NUMA节点0的内存资源,而不能使用其他节点的内存资源。
NUMA(Non-Uniform Memory Access)是一种计算机系统的体系结构,其中CPU和内存资源被分成多个节点(node)或区域(zone),每个节点的访问延迟和带宽不同。
接着往下看后面的日志信息。
Jul 19 07:10:23 GG-ops-yearning kernel: Mem-Info:
Jul 19 07:10:23 GG-ops-yearning kernel: active_anon:1701746 inactive_anon:730 isolated_anon:0#012 active_file:1379 inactive_file:2674 isolated_file:73#012 unevictable:0 dirty:3 writeback:0 unstable:0#012 slab_reclaimable:22823 slab_unreclaimable:22389#012 mapped:650 shmem:2732 pagetables:42746 bounce:0#012 free:45069 free_pcp:0 free_cma:0
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 DMA free:15908kB min:136kB low:168kB high:204kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
Jul 19 07:10:23 GG-ops-yearning kernel: lowmem_reserve[]: 0 2813 7533 7533
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 DMA32 free:111776kB min:25188kB low:31484kB high:37780kB active_anon:2573912kB inactive_anon:456kB active_file:4076kB inactive_file:5496kB unevictable:0kB isolated(anon):0kB isolated(file):252kB present:3111608kB managed:2881176kB mlocked:0kB dirty:12kB writeback:0kB mapped:2328kB shmem:1448kB slab_reclaimable:31344kB slab_unreclaimable:24556kB kernel_stack:6864kB pagetables:45460kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:14418 all_unreclaimable? yes
Jul 19 07:10:23 GG-ops-yearning kernel: lowmem_reserve[]: 0 0 4719 4719
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 Normal free:52592kB min:42252kB low:52812kB high:63376kB active_anon:4233072kB inactive_anon:2464kB active_file:1440kB inactive_file:5200kB unevictable:0kB isolated(anon):0kB isolated(file):40kB present:4980736kB managed:4835924kB mlocked:0kB dirty:0kB writeback:0kB mapped:272kB shmem:9480kB slab_reclaimable:59948kB slab_unreclaimable:65000kB kernel_stack:10240kB pagetables:125524kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:10151 all_unreclaimable? yes
Jul 19 07:10:23 GG-ops-yearning kernel: lowmem_reserve[]: 0 0 0 0
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 DMA: 14kB (U) 08kB 016kB 132kB (U) 264kB (U) 1128kB (U) 1256kB (U) 0512kB 11024kB (U) 12048kB (M) 34096kB (M) = 15908kB
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 DMA32: 217324kB (UEM) 26728kB (UEM) 22316kB (UEM) 032kB 064kB 0128kB 0256kB 0512kB 01024kB 02048kB 04096kB = 111872kB
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 Normal: 124724kB (UEM) 3868kB (UM) 316kB (M) 032kB 064kB 0128kB 0256kB 0512kB 01024kB 02048kB 04096kB = 53024kB
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB
Jul 19 07:10:23 GG-ops-yearning kernel: Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB
Jul 19 07:10:23 GG-ops-yearning kernel: 6892 total pagecache pages
Jul 19 07:10:23 GG-ops-yearning kernel: 0 pages in swap cache
Jul 19 07:10:23 GG-ops-yearning kernel: Swap cache stats: add 0, delete 0, find 0/0
Jul 19 07:10:23 GG-ops-yearning kernel: Free swap = 0kB
Jul 19 07:10:23 GG-ops-yearning kernel: Total swap = 0kB
Jul 19 07:10:23 GG-ops-yearning kernel: 2027084 pages RAM
Jul 19 07:10:23 GG-ops-yearning kernel: 0 pages HighMem/MovableOnly
Jul 19 07:10:23 GG-ops-yearning kernel: 93832 pages reserved
这条日志信息包含了系统的内存信息(Mem-Info)。它提供了关于系统内存使用情况的详细信息,包括当前可用内存、活动的内存、不活动的内存、缓存、页表等等。
如上面日志中的"active_anon"、“inactive_anon”、“active_file”、"inactive_file"等字段表示不同类型的内存使用情况。
“active_anon”:表示正在使用的匿名内存(未命名的内存块,如进程栈和堆等)大小,单位为页(page),在这个例子中是1701746页。
“inactive_anon”:表示不活跃的匿名内存大小,单位为页,在这个例子中是730页。
“active_file”:表示正在使用的文件内存大小,单位为页,在这个例子中是1379页。
“inactive_file”:表示不活跃的文件内存大小,单位为页,在这个例子中是2674页。
“isolated_anon"和"isolated_file”:表示被隔离的匿名内存和文件内存大小。在这个例子中,都是0页。
“unevictable”:表示不能被释放的内存大小,单位为页,在这个例子中是0页。
“dirty”:表示脏内存大小,即需要写回到磁盘的内存大小,单位为页,在这个例子中是3页。
“writeback”:表示正在写回到磁盘的内存大小,单位为页,在这个例子中是0页。
“unstable”:表示不稳定的内存大小,单位为页,在这个例子中是0页。
“slab_reclaimable”:表示可回收的内核对象内存大小,单位为字节,在这个例子中是22823字节。
“slab_unreclaimable”:表示不可回收的内核对象内存大小,单位为字节,在这个例子中是22389字节。
“mapped”:表示映射内存大小,单位为页,在这个例子中是650页。
“shmem”:表示共享内存大小,单位为页,在这个例子中是2732页。
“pagetables”:表示页表内存大小,单位为页,在这个例子中是42746页。
“bounce”:表示反弹缓冲区内存大小,单位为页,在这个例子中是0页。
“free”:表示可用内存大小,单位为页,在这个例子中是45069页。
“free_pcp"和"free_cma”:表示保留给per-CPU变量和DMA内存的可用内存大小,单位为页。在这个例子中,它们都是0页。
可与看到可用内存大小free,只有45069页4KB(页面大小)= 176MB
再往下走,可以看到系统的内存分为三个区域:DMA、DMA32和Normal。每个区域的可用内存、已使用内存、缓存、页表等等的详细信息都有列出。
例如,"Node 0 DMA free:15908kB"表示DMA区域当前可用的内存为15908kB。"Node 0 DMA32 free:111776kB"表示DMA32区域当前可用的内存为111776kB。"Node 0 Normal free:52592kB"表示Normal区域当前可用的内存为52592kB。
此外,日志信息还提供了有关Huge pages、Swap Cache和Page Cache的信息,以及系统中的总内存页数和可用的Swap空间。
这些信息对于了解系统的内存使用情况以及可能的内存问题非常有用。管理员可以使用这些信息来诊断内存问题,并采取必要的措施来优化系统的内存使用情况。
DMA、DMA32和Normal
DMA、DMA32和Normal是Linux内核中用于描述内存区域的术语。DMA代表Direct Memory Access,表示直接内存访问。DMA区域是系统中的低端内存区域,通常用于设备驱动程序和其他需要访问物理内存的程序。它包括0到16MB的物理内存地址范围。DMA32区域是介于DMA和Normal之间的一个区域,它包括16MB到4GB的物理内存地址范围。DMA32区域用于支持更大的内存访问,如大容量磁盘和网络存储设备。它通常用于服务器和工作站等高端系统。Normal区域是系统中的高端内存区域,它包括4GB到机器上限的物理内存地址范围。Normal区域通常用于操作系统和应用程序。
接着往下看后面的日志信息。
Jul 19 07:10:23 GG-ops-yearning kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Jul 19 07:10:23 GG-ops-yearning kernel: [ 381] 0 381 35462 99 74 0 0 systemd-journal
Jul 19 07:10:23 GG-ops-yearning kernel: [ 419] 0 419 11410 196 22 0 -1000 systemd-udevd
Jul 19 07:10:23 GG-ops-yearning kernel: [ 543] 0 543 13883 113 28 0 -1000 auditd
Jul 19 07:10:23 GG-ops-yearning kernel: [ 568] 999 568 153059 1653 63 0 0 polkitd
Jul 19 07:10:23 GG-ops-yearning kernel: [ 573] 0 573 6703 198 18 0 0 systemd-logind
Jul 19 07:10:23 GG-ops-yearning kernel: [ 574] 81 574 14594 213 32 0 -900 dbus-daemon
Jul 19 07:10:23 GG-ops-yearning kernel: [ 592] 998 592 29452 128 30 0 0 chronyd
Jul 19 07:10:23 GG-ops-yearning kernel: [ 837] 0 837 25735 513 47 0 0 dhclient
Jul 19 07:10:23 GG-ops-yearning kernel: [ 898] 0 898 3793730 864300 18 0 0 node_exporter
Jul 19 07:10:23 GG-ops-yearning kernel: [ 900] 0 900 143571 3340 98 0 0 tuned
Jul 19 07:10:23 GG-ops-yearning kernel: [ 911] 0 911 171069 7440 66 0 -999 containerd
Jul 19 07:10:23 GG-ops-yearning kernel: [ 955] 0 955 10890 393 21 0 0 AliYunDunUpdate
Jul 19 07:10:23 GG-ops-yearning kernel: [ 1029] 0 1029 5732 90 16 0 0 argusagent
Jul 19 07:10:23 GG-ops-yearning kernel: [ 1039] 0 1039 359960 3763 68 0 0 /usr/local/clou
Jul 19 07:10:23 GG-ops-yearning kernel: [ 1210] 0 1210 22536 278 41 0 0 master
上面的日志信息提供了系统当时运行的进程快照,包括进程ID(PID)、用户ID(UID)、总虚拟内存(total_vm)、驻留内存(rss)、页表项数(nr_ptes)、交换项数(swapents)等。每行日志信息对应一个进程。
“[ pid ]”:表示进程ID(PID),用方括号括起来。
“uid”:表示用户ID(UID),即运行该进程的用户ID。
“tgid”:表示进程组ID(TGID),即进程的父进程ID。
“total_vm”:表示进程使用的总虚拟内存大小,单位为页。
“rss”:表示进程使用的驻留内存大小,即实际占用的物理内存大小,单位为页。
“nr_ptes”:表示进程使用的页表项数。
“swapents”:表示进程使用的交换项数。
“oom_score_adj”:表示进程的OOM分数调整值,用于调整进程在内存不足时被杀死的优先级。较低的值表示进程更容易被杀死。
“name”:表示进程的名称。
这时我们就可以利用这些信息来了解当时系统中运行的进程的情况,包括使用的内存、资源占用情况等,找到是哪个进程占用内存过大,导致资源不足。
在这个例子中,我们可以看到pid是898的进程,占用资源非常大,rss为864300页,总大小864300*4K = 3.3G
好了,通过上面的分析,我们已经找到了导致问题的进程了,接下来就是分析为什么该进程占用内存大的问题了。详细的就不展开了。
回顾我们上述的过程,可以看到通过分析oom-killer记录的日志信息,我们可以得到很多有用的信息,这些信息对于了解系统的内存使用情况和性能瓶颈非常有用。管理员可以使用这些信息来优化系统的内存管理和配置,以实现更好的性能和可靠性。
总结
在分析历史时刻出现的服务器内存飙升的问题时,我们可以利用linux的oom-killer机制,查看系统日志中的oom-killer日志信息,该日志记录了Linux内核如何通过杀死进程来解决内存不足的问题。通过这些信息,以确定飙升发生的时间点,并查找与内存相关的任何错误或异常情况。
oom-killer是Linux系统中的一个内核机制,用于在系统内存不足的情况下,杀死一些进程以释放内存。
Linux 的每个进程都有一个 oom_score (位于/proc//oom_score),这个值越大,就越有可能被 OOM Killer 选中。oom_score 和 oom_score_adj (OOM score adjustment)是 Linux 内核中用于控制 OOM killer 行为的两个参数。
我们可以调整oom_score_adj的值来防止服务器上比较重要的应用被kill,比如例如 核心的应用进程,我们想尽量避免它被 OOM Killer 杀死,这时候就可以调低它的 oom_score_adj 的值。
参考:
http://senlinzhan.github.io/2017/07/03/oom-killer/ https://www.kernel.org/doc/gorman/html/understand/understand016.html https://www.percona.com/blog/out-of-memory-killer-or-savior/
Java进程 OOM的多种情况
济南小老虎2023-03-16
411
摘要
OOM 其实有多种:
第一类是JVM原生自发处理的, 这种也分为多种情况.
- 堆区使用了比较多,并且大部分对象都还有引用, GC不出来可用内存,
这是要给对象申请较大的内存空间时就会出现OOM的报错. - 除了IP 下一条命令指针的内存的区域, 其他任何区域都存在OOM的风险.
比如metadata,codecache,以及栈空间, 当然metadata一般时无限制的.
栈空间一般是stack over flow的提示信息.
第二类 操作系统进行的处理.
当系统的内存使用较高的时候, 剩余空间几乎没有
此时如果有JAVA本服务的线程,或者是其他进程要跟操作系统申请内存使用.
操作系统发现内存已经不足以支撑, 就会选择oom_score 得分比较高的进程进行kill
如果正好关闭了swap, 不会进行swapout/swapin的操作, 系统可能使用的很流畅.
突然就会宕机. 这种通过分析JVM是较难进行处理的.
问题现象
今天下午现场一台机器突然宕机连不上.
运维同事立即进行了服务启动.
晚上时有人反馈系统出现宕机影响使用.
这边进行了一下简单的分析. 结果其实都是摘要里面的.
但是想把过程简单描述一下, 以便备忘.
分析过程
方法很简单通过如下命令就可以
cat var/log/messages |grep out_of_memory+ -C 20
其实我这边还可以通过
dmesg -T |grep -i oom 进行查看
但是发现不如上面一个命令系统详实.
配合 系统的日志就可以进行分析.
系统日志信息
Mar 15 14:58:52 localhost kernel: OkHttp Connecti invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Mar 15 14:58:52 localhost kernel: OkHttp Connecti cpuset=/ mems_allowed=0
Mar 15 14:58:52 localhost kernel: CPU: 4 PID: 23890 Comm: OkHttp Connecti Kdump: loaded Not tainted 3.10.0-1127.el7.x86_64 #1
Mar 15 14:58:52 localhost kernel: Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 12/12/2018
Mar 15 14:58:52 localhost kernel: Call Trace:
…
Mar 15 14:58:52 localhost kernel: Out of memory: Kill process 25813 (java) score 757 or sacrifice child
Mar 15 14:58:52 localhost kernel: Killed process 23697 (sh), UID 0, total-vm:113284kB, anon-rss:184kB, file-rss:0kB, shmem-rss:0kB
Mar 15 14:58:52 localhost kernel: Spring session invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Mar 15 14:58:52 localhost kernel: Spring session cpuset=/ mems_allowed=0
Mar 15 14:58:52 localhost kernel: CPU: 6 PID: 15972 Comm: Spring session Kdump: loaded Not tainted 3.10.0-1127.el7.x86_64 #1
Mar 15 14:58:52 localhost kernel: Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 12/12/2018
Mar 15 14:58:52 localhost kernel: Call Trace:
Mar 15 14:58:52 localhost kernel: Call Trace:
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
Mar 15 14:58:52 localhost kernel: [
进程信息
可以看到 上面一个被kill的信息为:
Out of memory: Kill process 25813
查看系统宕机之前的日志就可以确定是否是本进程
2023-03-15 14:47:56,845 localhost.localdomain ERROR [25813.85ec182.314.1]
[Schd] org.hibernate.engine.jdbc.spi.SqlExceptionHelper
看到 ERROR 后面的第一个就是进程信息,致辞确认是被linux系统给kill了.
问题原因分析与对策
- GC日志里面没有任何Full GC多次出现的现象.
宕机之前系统运行是非常稳定的. - 之前系统多次出现过swap分区使用过多的情况.导致卡顿.
为了避免过于卡顿影响业务,已经将swap分区关闭. - dmesg 发现系统经常有OOM的情况.
所以分析下来,在不特备影响系统性能的情况下只有两种解决策略: - 适当降低jvm的堆区, 也就是降低 -Xms和-Xmx 给其他使用者空闲出来一部分资源.
- 提高资源配置. 加大内存, 避免出现宕机的问题.
问题的延伸思考
- 一个机器. 可以分配多少内存给JVM的堆区?
- 内存应该如何分配,堆区,栈区,方法区,元数据区,本地内存等的配置. 以及留出部分内存给想系统和文件缓存使用.
- docker 容器模式下. 如果使用ramMaxPercentage 的模式进行设置.
堆区太小肯定性能不好, 太大了之后FullGC的STW时间需要控制.
之前对功能还有自动化的环境进行过 NMT的内存跟踪, 堆区,一般占用总Java进程的75左右的内存.
所以理论上 排除到系统必须的内存和一定的文件缓存之后. 堆区应该不能占用超过七成的可用内存
避免出现宕机的情况.




