页缓存是否属于Java堆外内存?Spark K8s任务OOM排查
页缓存是否属于JVM堆外内存?Spark任务K8s环境OOM排查
核心问题
确认:Java进程以非Direct_IO模式向磁盘写入文件时,操作系统生成的页缓存(page cache)是否属于JVM堆外内存?
背景
在K8s环境运行Scala Spark任务,已设置0.2的内存开销比例,但任务频繁因OOM被终止。Spark UI显示堆内存与堆外内存的峰值总和远低于容器内存限制,但dmesg日志显示Java进程被杀死时占用了容器全部内存,怀疑是页缓存占用剩余内存导致OOM。
资源占用对比
Spark UI统计数据
已终止的executor1内存占用:
- 堆内存:21.1 GiB
- 堆外内存:142.2 MiB
dmesg日志记录(进程被杀死时)
[Mon Feb 20 03:36:35 2023] memory: usage 25907200kB, limit 25907200kB, failcnt 1911352 [Mon Feb 20 03:36:35 2023] memory+swap: usage 0kB, limit 9007199254740988kB, failcnt 0 [Mon Feb 20 03:36:35 2023] kmem: usage 106724kB, limit 9007199254740988kB, failcnt 0 [Mon Feb 20 03:36:35 2023] Memory cgroup stats for /kubepods/pod320ce7f2-aa63-4f3f-9fbe-2ae0be48736c: [Mon Feb 20 03:36:35 2023] anon 25102856192 file 1315708928 kernel_stack 1069056 slab 52314112 sock 0 shmem 0 file_mapped 0 file_dirty 1316130816 file_writeback 0 anon_thp 20558381056 inactive_anon 0 active_anon 25102508032 inactive_file 328622080 active_file 987414528 unevictable 0 slab_reclaimable 43982848 slab_unreclaimable 8331264 pgfault 11556633 pgmajfault 0 workingset_refault 814704 workingset_activate 165 workingset_nodereclaim 0 pgrefill 12258140 pgscan 15669889 pgsteal 2556626 pgactivate 13111197 pgdeactivate 12258004 pglazyfree 0 pglazyfreed 0 thp_fault_alloc 11253 thp_collapse_alloc 0 [Mon Feb 20 03:36:35 2023] Tasks state (memory values in pages): [Mon Feb 20 03:36:35 2023] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Mon Feb 20 03:36:35 2023] [ 168097] 65535 168097 241 1 28672 0 -998 pause [Mon Feb 20 03:36:35 2023] [ 168142] 0 168142 571 192 49152 0 -997 tini [Mon Feb 20 03:36:35 2023] [ 168217] 0 168217 7542445 6135546 50069504 0 -997 java
内核调用栈
[Mon Feb 20 03:36:55 2023] Call Trace: [Mon Feb 20 03:36:55 2023] dump_stack+0x57/0x6d [Mon Feb 20 03:36:55 2023] dump_header+0x4f/0x200 [Mon Feb 20 03:36:55 2023] oom_kill_process+0xec/0x140 [Mon Feb 20 03:36:55 2023] out_of_memory+0x117/0x570 [Mon Feb 20 03:36:55 2023] mem_cgroup_out_of_memory+0xbb/0xd0 [Mon Feb 20 03:36:55 2023] try_charge+0x762/0x7c0 [Mon Feb 20 03:36:55 2023] ? ext4_do_update_inode+0x4ff/0x7b0 [Mon Feb 20 03:36:55 2023] mem_cgroup_try_charge+0x75/0x190 [Mon Feb 20 03:36:55 2023] __add_to_page_cache_locked+0x21a/0x3d0 [Mon Feb 20 03:36:55 2023] ? scan_shadow_nodes+0x30/0x30 [Mon Feb 20 03:36:55 2023] add_to_page_cache_lru+0x4f/0xd0 [Mon Feb 20 03:36:55 2023] pagecache_get_page+0xea/0x2c0 [Mon Feb 20 03:36:55 2023] grab_cache_page_write_begin+0x23/0x40 [Mon Feb 20 03:36:55 2023] ext4_da_write_begin+0x11a/0x450 [Mon Feb 20 03:36:55 2023] generic_perform_write+0xb3/0x1b0 [Mon Feb 20 03:36:55 2023] __generic_file_write_iter+0x1aa/0x1d0 [Mon Feb 20 03:36:55 2023] ? generic_write_checks+0x68/0xc0 [Mon Feb 20 03:36:55 2023] ext4_file_write_iter+0xbd/0x360 [Mon Feb 20 03:36:55 2023] ? futex_wake+0x8f/0x180 [Mon Feb 20 03:36:55 2023] new_sync_write+0x125/0x1c0 [Mon Feb 20 03:36:55 2023] __vfs_write+0x29/0x40 [Mon Feb 20 03:36:55 2023] vfs_write+0xb1/0x1a0 [Mon Feb 20 03:36:55 2023] ksys_write+0xa7/0xe0 [Mon Feb 20 03:36:55 2023] __x64_sys_write+0x1a/0x20 [Mon Feb 20 03:36:55 2023] do_syscall_64+0x5e/0x200 [Mon Feb 20 03:36:55 2023] entry_SYSCALL_64_after_hwframe+0x44/0xa9
结论与解决方案
1. 页缓存不属于JVM堆外内存
JVM堆外内存指JVM直接管理的非堆内存区域,包括DirectByteBuffer、JNI分配内存、Metaspace、Code Cache等。而操作系统页缓存是内核层面独立管理的文件缓存,完全脱离JVM内存统计范畴,不会被Spark UI计入堆外内存。
2. OOM原因确认
从dmesg日志的file字段(约1.22GiB)和调用栈可以看出,OOM发生在写入文件时向页缓存分配内存的过程中。K8s容器内存限制针对整个Pod的所有内存使用(用户进程内存、内核页缓存、Slab等),而Spark UI仅统计JVM自身内存,因此页缓存占用过多会导致Pod总内存超限,触发OOM Killer杀死Java进程。
3. 解决方案建议
- 使用Direct IO绕过页缓存:配置Spark输出使用Direct IO(如HDFS的
dfs.direct.io参数),直接写入磁盘,避免生成页缓存。 - 调整容器内存配置:适当增加容器内存限制,为页缓存预留空间;或配置K8s QoS为
Burstable,允许一定内存超用(需集群支持)。 - 优化Spark写入策略:合并输出文件,减少小文件数量;采用异步、批量写入方式,降低页缓存瞬时占用。
- 调整内核页缓存回收策略:通过sysctl修改
vm.dirty_ratio、vm.dirty_background_ratio等参数,让内核更及时刷入脏页,释放页缓存内存。
内容的提问来源于stack exchange,提问作者Mifan Indian
相关产品推荐
相关产品推荐

