Java内存排查太难?阿里云操作系统控制台上线「内存诊断」新利器

简介: 帮助用户结合应用和操作系统的角度,快速揪出 Java 应用内存占用的元凶。


背景

随着汽车行业加速智能化转型,从传统线下 IDC 集群向云端迁移并进行容器化改造,经常会遇到关于 Pod 内存异常、Pod发生 OOMKilled 的问题, 这些问题主要的矛盾点在于:

1、Pod(容器)内存占用比 JVM 内存监控(堆内和堆外内存)占用大很多。

2、总是有一部分消失的内存无法找出具体是哪部分占用。

3、同一业务同一 JDK 版本,切换 OS 或容器化改造之后,才出现了 1、2 现象。

虽然 Java 工具千千万,但是选用什么工具排查起这类 Java 内存问题也是一个头疼的问题;甚至有时候翻遍了工具百宝箱,最后还是没有排查出问题的根因。经历过这些问题的洗礼之后,我们也从中总结了一些排查思路,并沉淀成一个阿里云操作系统控制台的 Java 内存诊断功能,帮助用户结合应用和操作系统的角度,快速揪出 Java 应用内存占用的元凶。

消失的 Java 内存

Java 内存组成

为了找出消失的内存,我们首先要了解 Java 进程的主要内存组成以及现有工具和监控主要覆盖的部分;如下图所示可分为:

JVM 内存

  • 堆内存:可通过 -Xms/-Xmx 参数控制,内存大小可通过 memorymxbean 等获取。
  • 堆外内存:包括元空间、压缩类空间、代码缓冲区、直接缓冲、线程栈等内存组成;它们分别可以通过-XX:MaxMetaspaceSize(元空间)、-XX:CompressedClassSpaceSize (压缩类空间)、 -XX:ReservedCodeCacheSize( 代码缓冲区)、-XX:MaxDirectMemorySize (直接缓冲)、-Xss(线程栈)参数限制。

非 JVM 内存

  • JNI本地内存:即通过本地方法接口调用 C、C++ 代码(原生库),并在这部分代码中调用 C 库(malloc)或系统调用(brk、mmap)直接分配的内存。

常见 Java 内存黑洞

JNI 内存

通过对 Java 内存组成的了解,我们其实已经可以揭开第一个容易造成内存黑洞的隐藏 Boss-JNI 内存,因为这部分内存暂时没有工具可以获取其占用大小。

通常来说,编写相关业务代码的同学会认为代码中没有使用本地方法直接调用 C 库,所以不会存在这些问题,但是代码中引用的各种包却有可能会使用到 JNI 内存,比如说经典的使用 ZLIB 压缩库不当导致的 JNI 泄漏问题[2]

LIBC 内存

熟悉 Java 的同学都知道,JVM 是由 C++ 编写的,JVM 调用 malloc、free 申请/释放内存的过程中其实还要经过一个二道贩子 libc 库;以 gibc 中默认的内存分配器 ptmalloc 为例:

chunk 是 glibc 堆内存分配的最小单位,表示一段连续的内存区域。ptmalloc 会为每一个线程维护一个 Arena,每一个 Arena 中会有一个大 chunk(Top chunk)和 chunk 的缓存集合(bins);当应用通过 malloc、free 申请/释放内存时、ptmalloc 会优先从 bins 中拿取和释放 chunk,如果没有符合要求的 bins,再从 top chunk 里面获取、再没有就通过 brk、mmap 系统调用从 OS 获取。

从上面的流程,我们可以发现,libc 作为二道贩子,很有可能多申请、扣留一些内存,从而导致 JVM 内存和进程实际内存的差异;我们也总结一下 libc 常见的一些问题:

  • 多线程 64M Arena 内存占用,libc 会为每个线程创建一个 64M 大小的 Arena,默认配置下在线程数量较多时会导致一定的内存浪费 [3]
  • Top chunk 由于内存空洞导致无法及时释放回 OS [4]
  • bins 缓存,JVM 释放的内存被缓存在 bins 中,导致内存差异 [4]

透明大页

到了 OS 层,Linux 中的透明大页(Transparent Huge Page)机制也是造成 JVM 内存和实际内存差异的一大元凶。简单来说,THP 机制就是 OS 会将 4kb 页变成 2M 的大页,从而减少 TLB miss 和缺页中断,提升应用性能,但是也带来了一些内存浪费。如应用申请了一段 2M 的虚拟内存,但实际只用了里面的 4kb,但是由于 THP 机制,OS 已经分配了一个 2M 的页了[5]

通过阿里云操作系统控制台揪出Java内存占用元凶

操作系统控制台是阿里云推出的一站式运维管理平台,充分结合了阿里在百万服务器运维领域的丰富经验。集成了监控、系统诊断、持续追踪、AI 可观测、集群健康度和 OS Copilot 等核心功能,专门应对云端高负载、网络延迟抖动、内存泄漏、内存溢出(OOM)、宕机、I/O 流量分析及性能抖动等各种复杂问题。

下面将以汽车行业客户在从线下 idc 集群迁移至云上 ACK 集群时遇到的由于 JNI 内存泄漏导致 Pod 频繁 OOM 为例,介绍如何通过操作系统控制台的内存全景分析功能[5]来一步步找出 Java 内存占用的元凶。

背景:

客户云上多个集群多个服务中的一些 Java 业务 pod 在没有任何服务异常、请求异常、流量异常的迹象下会偶发 OOM,且从 jvm 监控上内存也没有很大的波动(客户设置 5G limit,正常水位在 3G 左右)。

排查过程:

  • 尝试在内存高水位时对 Pod 发起内存全景分析,我们可以了解到当 Pod 中容器内存使用已经接近 limit,从诊断结论和容器内存占用分析中,我们可以看到容器内存主要是由于 Java 进程内存占用导致。

对 Java 进程发起内存分析,查看诊断报告。报告展示了 Java 进程所在 Pod 和容器的 rss 和 WorkingSet(工作集)内存信息、进程 Pid、JVM 内存使用量(即 JVM 视角的内存使用量)、Java 进程内存使用量(进程实际占用内存),进程匿名用量以及进程文件内存用量。

通过诊断结论和 Java 内存占用饼图我们可以发现,进程实际内存占用比 JVM 监控显示的内存占用大 570M,全都由 JNI 内存所贡献。

开启 JNI(Java Native Interface)内存分配 profiling,报告列出当前 Java 进程 JNI 内存分配调用火焰图,火焰图中为所有分配 JNI 内存的调用路径。(说明:由于是采样采集,火焰图中的内存大小不代表实际分配大小)。

  • 从内存分配火焰图中,我们可以看到主要的内存申请为 C2 compiler 正在进行代码 JIT 预热;
  • 但是由于诊断的过程中没有发现 pod 有内存突增;所以我们进一步借助可以常态化运行的 Java CPU 热点追踪功能[7]尝试抓取内存升高时的进程热点,并通过热点对比[8]尝试对内存正常时的热点进行对比。

  • 通过热点栈和热点分析对比,发现内存突增时间点的 CPU 栈也是 c2 compiler 的JIT 栈,且 c2 compiler 热点前有部分业务流量突增,且业务代码使用了大量反射操作(反射操作会导致 c2 compiler 进行新的预热)。

排查结论:

C2 compiler JIT 过程申请 JNI 内存,且由于 glibc 内存空洞等原因导致申请内存放大且延时释放。

缓解方法:

1.调整 C2 compiler 参数,让其编译策略更保守,可以尝试调整相关参数,观察内存消耗变化。

2.调整 glibc 环境变量 MALLOC_TRIM_THRESHOLD_,让 glibc 及时将内存释放回操作系统。

联系我们

您在使用操作系统控制台的过程中,有任何疑问和建议,可以搜索群号:94405014449 加入钉钉群反馈,欢迎大家扫码加入交流。

相关链接:

【1】阿里云操作系统控制台PC端链接:https://alinux.console.aliyun.com/

【2】java.util.zip内存泄露:https://bugs.openjdk.org/browse/JDK-8257032

【3】glibc 64M arena内存浪费:https://bugs.openjdk.org/browse/JDK-8193521

【4】glibc top chunk/fast bin内存不回收:https://wenfh2020.com/2021/04/08/glibc-memory-leak/#332-fast-bins-%E7%BC%93%E5%AD%98

【5】go应用由于thp导致内存膨胀:https://github.com/golang/go/issues/64332

【6】操作系统控制台内存全景分析:https://help.aliyun.com/zh/alinux/user-guide/memory-panorama-analysis-function-instructions?spm=a2c4g.11186623.0.i1#undefined

【7】操作系统控制台热点追踪:https://help.aliyun.com/zh/alinux/user-guide/process-hotspot-tracking?spm=a2c4g.11186623.help-menu-2632541.d_2_0_2_0.674a698dkLRagc&scm=20140722.H_2849500._.OR_help-T_cn~zh-V_1

【8】操作系统控制台热点对比分析:https://help.aliyun.com/zh/alinux/user-guide/hot-spot-comparative-analysis?spm=a2c4g.11186623.help-menu-2632541.d_2_0_2_1.118569efrdHqOx&scm=20140722.H_2849536._.OR_help-T_cn~zh-V_1

相关文章
|
5月前
|
缓存 Linux 云计算
OOM 杀进程 or 应用卡顿?该如何抉择
阿里云操作系统控制台推出了 FastOOM 功能,支持节点以及 Pod 级别的用户态 OOM 配置,通过提前介入杀进程的方式避 Near-OOM 导致的抖动夯机。
313 0
|
2月前
|
人工智能 前端开发 算法
大厂CIO独家分享:AI如何重塑开发者未来十年
在 AI 时代,若你还在紧盯代码量、执着于全栈工程师的招聘,或者仅凭技术贡献率来评判价值,执着于业务提效的比例而忽略产研价值,你很可能已经被所谓的“常识”困住了脚步。
1570 89
大厂CIO独家分享:AI如何重塑开发者未来十年
|
11天前
|
存储 缓存 调度
阿里云Tair KVCache仿真分析:高精度的计算和缓存模拟设计与实现
在大模型推理迈向“智能体时代”的今天,KVCache 已从性能优化手段升级为系统级基础设施,“显存内缓存”模式在长上下文、多轮交互等场景下难以为继,而“以存代算”的多级 KVCache 架构虽突破了容量瓶颈,却引入了一个由模型结构、硬件平台、推理引擎与缓存策略等因素交织而成的高维配置空间。如何在满足 SLO(如延迟、吞吐等服务等级目标)的前提下,找到“时延–吞吐–成本”的最优平衡点,成为规模化部署的核心挑战。
249 36
阿里云Tair KVCache仿真分析:高精度的计算和缓存模拟设计与实现
|
2月前
|
运维 监控 数据可视化
故障发现提速 80%,运维成本降 40%:魔方文娱的可观测升级之路
魔方文娱携手阿里云构建全栈可观测体系,实现故障发现效率提升 80%、运维成本下降 40%,并融合 AI 驱动异常检测,迈向智能运维新阶段。
366 47
|
1月前
|
监控 前端开发 数据可视化
Entity Explorer:基于 UModel 的实体探索平台
阿里云 Entity Explorer 正式发布:基于 UModel 的智能实体探索平台,实现亿级实体秒级检索、关系拓扑自动构建、详情页动态渲染,让可观测性从“数据堆砌”迈向“业务洞察”。
249 41
|
2月前
|
存储 算法 AliSQL
AliSQL 向量技术解析(一):存储格式与算法实现
AliSQL基于MySQL 8.0原生扩展向量处理能力,支持高达16383维的向量存储与计算,集成余弦相似度、欧式距离等函数,并通过HNSW算法实现高效近似最近邻搜索。借助结构化辅助表与精度压缩技术,兼顾检索精度与性能,结合数据字典适配保障DDL原子性,为推荐系统、AI应用提供开箱即用的高维向量检索解决方案。
AliSQL 向量技术解析(一):存储格式与算法实现
|
1月前
|
存储 人工智能 编译器
智驾大模型的「隐形战场」:当GPU堆不动了,行业拼什么?
与参会嘉宾一起探讨了如何通过技术协作加速智能驾驶的进步,分享了各自在自动驾驶技术栈中的前沿实践与生态思考。
|
2月前
|
人工智能 开发框架 缓存
2025 SECon × AgentX 大会:AI 原生应用架构专场精彩回顾 & PPT 下载
近日,2025 SECon × AgentX大会——AI 原生应用架构专场圆满落幕,本次专场阿里云联合信通院共同出品,现场吸引了 80+ 名技术从业者深度参与。活动聚焦 AI 时代软件架构的核心命题,深度分享了 AI 原生应用架构趋势与实践、AgentScope 开发框架、AI 开放平台、大模型可观测 & AIOps 等热门技术议题,探讨从基础设施到应用层的协同演进策略与工程实践。
250 18
|
2月前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
本文系统解析Android崩溃捕获原理,涵盖Java与Native层崩溃的捕获机制、核心技术难点及解决方案,介绍基于Breakpad的Minidump生成、堆栈回溯与符号化解析实践,实现崩溃信息可靠采集与精准归因。
238 5

热门文章

最新文章