当Java虚拟机遇上Linux Arena内存池

简介: 故障案例一 系统环境: RHEL 6.8 64-bit(glibc 2.12)、Sun JDK 6u45 64-bit、WLS 10.3.6 故障现象: 这里引用一下客户当时发邮件时提出的问题描述吧。


故障案例一

系统环境:


RHEL 6.8 64-bit(glibc 2.12)、Sun JDK 6u45 64-bit、WLS 10.3.6

故障现象:


这里引用一下客户当时发邮件时提出的问题描述吧。
下面pid 6287 weblogic进程占用7.6G的物理内存,之前只占用5G内存。我发现只有系统有空余的内存,就会被java给吃掉,为什么内存占用越来越多?
通过jmap -histo:live 6287 查看内存只占用800多MB。
Total 12415620 891690640

1

此时,操作系统内存几乎耗尽,而且用了很多Swap交换分区内存,系统性能并不是很好。

故障分析:


刚开始看到这个问题时,首先考虑可能是Native Memory Leak或JDK的Bug,然后看了下那些WebLogic进程的命令行参数:
/data/jdk1.6.0_45/bin/java -server -Xms2560m -Xmx2560m .....
AI 代码解读

从JDK入手
一看,已经是6u45了,Sun Java SE Public版的最终版本了,找来找去也没找到匹配的Bug(当时还真找到一个看着很像的,JDK-2172773 : JVM sometimes would suddenly consume significant amount of memory,但人家是在6u14b01、5u16rev这两个版本开始,都已经修复了),看来不能从JDK Bug这个方向入手分析了。
从Native Memory Leak入手
但是这个JDK版本也比较尴尬,没有提供Native Memory Trace的功能参数或命令支持(from 7u40版本开始提供),要知道Sun Java SE内部的内存区域很复杂,常见或不常见的很多区域,下面拿JDK 8版本(6版本大同小异)的内存区域为例展示一下:

2

没有直接的诊断工具的情况下,只能通过一些操作系统命令对这些RES、VIRT内存占用都高的JVM进程的内存使用输出结果做比较,以从中找出一些蛛丝马迹。最终,确定使用pmap这个命令(程序),结果看到如下的输出结果:

3

这里发现一个规律,65484 + 52 = 65536 KB, 65420 + 116 = 65536 KB, 65036 + 500 = 65536 KB .... ,进程内有大量的这种64MB大小的连续内存块。
然后,就是需要知道这是什么东东,Google一把,得知anon是Anonymous memory段的缩写。
Anonymous memory is a memory mapping with no file or device backing it.
This is how programs allocate memory from the operating system for use
by things like the stack and heap.
Anonymous memory的使用会使虚拟内存(VIRT)、物理内存(RSS)使用率上升。
而且,找到两篇讲的很清晰的文档了:

14


那这个问题就是Arena内存池数太多,且分配使用的内存较多,不断上涨,导致的WebLogic/Java虚拟机进程RES、VIRT内存使用超高。

解决办法:


直接想到的解决思路就是限制Arena内存池的个数。考虑到Arena内存池的主要是用来提高glibc内存分配性能的,而且根据Hadoop、Redis等产品的最佳实践建议,尝试设置MALLOC_ARENA_MAX环境变量值为4:
export MALLOC_ARENA_MAX=4
AI 代码解读

设置完重启WebLogic,然而意外的是,设置完以后Java虚拟机/WebLogic进程RES、VIRT内存使用依然很高:

4

后来我查到glibc 2.12版本有几个Arena内存管理的Bug,可能导致参数设置不生效或生效后内存继续往上涨:
Bug 799327 - MALLOC_ARENA_MAX=1 does not work in RHEL 6.2(glibc 2.12)
Bug 20425 - unbalanced and poor utilization of memory in glibc arenas may cause memory bloat and subsequent OOM
Bug 11261 - malloc uses excessive memory for multi-threaded applications
然后,我们考虑到将MALLOC_ARENA_MAX设置为4已经影响了一些Arena内存池管理上的一些性能,要继续使用MALLOC_ARENA_MAX参数,就需要升级glibc的版本,升级完还不确定高版本的glibc与其他包兼容性上有什么影响,毕竟是操作系统底层的包了,所以就直接使用了Google的tcmalloc替代操作系统自带的glibc管理内存。有资料显示,使用tcmalloc以后,Web Server的吞吐量得以提升(先尝试的jemalloc,但是启动后会影响操作系统命令的执行,所以,就用了tcmalloc):

5

替换为tcmalloc以后,WebLogic/Java虚拟机进程使用的RES、VIRT内存明显下降到合理值,问题得以解决。

故障案例二

系统环境:


RHEL 6.5 64-bit(glibc 2.12)、Sun JDK 5u22 32-bit、WLS 10.0.2

故障现象:


客户核心系统由于业务的需要,新加了一个节点,沿用原先的相同的操作系统、WebLogic、JDK版本,并且保证所有WebLogic参数配置都是相同的情况下,经常出现Java虚拟机Crash的情况:
file hs_err_pid28384.log : 
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  SIGSEGV (0xb) at pc=0xf6f8405d, pid=28384, tid=815790960
#
# Java VM: Java HotSpot(TM) Server VM (1.5.0_22-b03 mixed mode)
# Problematic frame:
# V  [libjvm.so+0x24405d]
#
......
AI 代码解读

故障分析:


由于这是32-bit的JDK,那就是Native Memory使用过高,超过了寻址空间的限制(4G,默认User Space : Kernel Space = 3 : 1,但在目前的Linux内核版本中,大多数32-bit的进程运行在64-bit操作系统上,几乎都可以用到所有的4G用户空间)。
在做分析之前,为扩大Native Memory空间,我降低了Java Heap Size(-Xms、-Xmx)和 Perm Size(-XX:PermSize、-XX:MaxPermSize)的值,以此来放缓Native Memory上涨的形势(客户不同意使用64-bit JDK)。
接下来,就是分析什么东东造成Native Memory使用持续上涨了。这里,还是用了pmap去看下Native Memory的使用和变化:

6

这里也看到了许多984 + 40 = 1024 KB, 1012 + 12 = 1024 KB, 988 + 36 = 1024 KB .... ,进程内有大量的这种1MB大小的连续内存块,而且,通过多次不同时间点的pmap -px输出结果来看,这种1MB大小的内存块还在不断增长。到这里,联想到上面的连续的64MB大小的内存快,迅速找到了当时留的文档链接

12

这篇文章里明确提到:
These memory pools are called arenas and the implementation is in arena.c. The first important macro is HEAP_MAX_SIZE which is the maximum size of an arena and it is basically 1MB on 32-bit and 64MB on 64-bit:
HEAP_MAX_SIZE = (2 * DEFAULT_MMAP_THRESHOLD_MAX)
32-bit [DEFAULT_MMAP_THRESHOLD_MAX = (512 * 1024)] = 1,048,576 (1MB)
64-bit [DEFAULT_MMAP_THRESHOLD_MAX = (4 1024 1024 * sizeof(long))] = 67,108,864 (64MB)
32-bit应用程序Arena的大小最大为1MB,64-bit应用程序最大为64MB,这次终于见识到了。
32-bit应用程序,sizeof(long) = 4 bit,那么这个计算系数就是 2(sizeof(long) == 4 ? 2 : 8)
按照Arena数量最大值的计算公式:
maximum number of arenas = NUMBER_OF_CPU_CORES (sizeof(long) == 4 ? 2 : 8) 计算,当前系统80核CPU,那么理论上该Java虚拟机进程最大的Arena值就是 80 2 * 1(MB)= 160MB,但实际上,通过pmap观察到这个进程这种1MB大小的匿名内存块都有700多MB,又看了下当前操作系统中glibc的版本是1.12,联想到故障案例一中设置的MALLOC_ARENA_MAX=4在1.12版本都不生效的问题,遇到这种现象就不足为奇了。

目前,RHEL 5.x、6.x、7.3中使用的glibc版本都比较旧(都是2012年及之前的版本了,7.3中使用的glibc版本是2.17,6.x中使用的glibc版本是2.12),可考虑在不是很重要的系统中保持glibc版本始终为最新,然后再观察Arena内存的使用。

7

解决办法:


这次直接设置MALLOC_ARENA_MAX=1,只保留main arena,禁用掉per thread arena内存池,使其与RHEL 5.x版本保持一致,问题得以解决,设置完,Java虚拟机不再Crash,pmap监控WebLogic/JVM进程使用的内存增长明显变少、变缓。当然,设置完MALLOC_ARENA_MAX=1,该WebLogic/JVM进程的Native Memory分配、重用、回收等性能多多少少会受到一些影响,也可以使用Google的tcmalloc解决。

总结

通过这两个故障案例可以看出,从glibc 2.11(为应用系统在多核心CPU和多Sockets环境中高伸缩性提供了一个动态内存分配的特性增强)版本开始引入了per thread arena内存池,Native Heap区被打散为sub-pools ,这部分内存池叫做Arena内存池。也就是说,以前只有一个main arena,目前是一个main arena(还是位于Native Heap区) + 多个per thread arena,多个线程之间不再共用一个arena内存区域了,保证每个线程都有一个堆,这样避免内存分配时需要额外的锁来降低性能。main arena主要通过brk/sbrk系统调用去管理,per thread arena主要通过mmap系统调用去分配和管理。
我们来看下线程申请per thread arena内存池的流程:
Unlimited MALLOC_ARENAS_MAX
  • Thread asks for an per thread arena
  • Thread gets an per thread arena
  • Thread fills arena, never frees memory
  • Thread asks for an new per thread arena
    .- ...........
  • When no more per thread arena will be created, reused_arena function will be called to reuse arena already existed.

我们知道了main arena、per thread arena,那么一个Java虚拟机进程究竟能创建多少个arena、每个arena的大小又是多少那?这部分理论知识比较常见,还不清楚的童鞋,我再啰嗦一下,贴一遍:
*一个32位的应用程序进程,最大可创建 2 CPU总核数个arena内存池(MALLOC_ARENA_MAX),每个arena内存池大小为1MB
一个64位的应用程序进程,最大可创建 8 CPU总核数个arena内存池(MALLOC_ARENA_MAX),每个arena内存池大小为64MB*
理论归理论,glibc 2.12版本(也就是RHEL 6.x中默认自带的)在arena内存分配和管理上,由于不少的Bug或目前我还没完全弄明白的理论的存在,实际上用pmap看到的1MB或64MB的anonymous memory(缩写为anon)并不完全遵循MALLOC_ARENA_MAX个数设置。
除故障案例一中提到的几处bug文章外,还有两个地方的文档显示,MALLOC_ARENA_MAX个数并不是按照设计那样的工作,多线程应用经常遇到RSS、VIRT内存持续升高的情况,尤其是CPU核数多的系统环境。
**glibc incorrectly allocated too much memory due to a race condition
within its own malloc routines. This could cause a multi-threaded
application to allocate more memory than was expected.**
13

如果不考虑内存分配的性能,遇到这样的问题时,可使用export MALLOC_ARENA_MAX=1禁用per thread arena,只用main arena,多个线程共用一个arena内存池。如果考虑到性能,可使用tcmalloc或jemalloc替代操作系统自带的glibc管理内存。
上面两个故障案例都是Sun HotSpot JVM的,另外,IBM JDK和Oracle JRockit在RHEL 6.x操作系统环境运行时,也会遇到Arena内存使用上的问题,详见:

11

原文发布时间为:2017-10-27
本文作者:刘韬
本文来自云栖社区合作伙伴“数据和云”,了解相关信息可以关注“数据和云”微信公众号shuju

目录
打赏
0
0
0
0
73530
分享
相关文章
|
3月前
|
linux 手动释放内存
在 Linux 系统中,内存管理通常自动处理,但业务繁忙时缓存占用过多可能导致内存不足,影响性能。此时可在业务闲时手动释放内存。
161 17
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
134 29
JVM简介—1.Java内存区域
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
72 27
【YashanDB知识库】kettle同步大表提示java内存溢出
在数据导入导出场景中,使用Kettle进行大表数据同步时出现“ERROR:could not create the java virtual machine!”问题,原因为Java内存溢出。解决方法包括:1) 编辑Spoon.bat增大JVM堆内存至2GB;2) 优化Kettle转换流程,如调整批量大小、精简步骤;3) 合理设置并行线程数(PARALLELISM参数)。此问题影响所有版本,需根据实际需求调整相关参数以避免内存不足。
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
141 48
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
65 0
|
2月前
|
java设置栈内存大小
在Java应用中合理设置栈内存大小是确保程序稳定性和性能的重要措施。通过JVM参数 `-Xss`,可以灵活调整栈内存大小,以适应不同的应用场景。本文介绍了设置栈内存大小的方法、应用场景和注意事项,希望能帮助开发者更好地管理Java应用的内存资源。
55 4
|
2月前
|
Linux系统内存使用优化技巧
交换空间(Swap)的优化 禁用 Swap sudo swapoff -a 作用:这个命令会禁用系统中所有的 Swap 空间。swapoff 命令用于关闭 Swap 空间,-a 参数表示关闭 /etc/fstab 文件中配置的所有 Swap 空间。 使用场景:在高性能应用场景下,比如数据库服务器或高性能计算服务器,禁用 Swap 可以减少磁盘 I/O,提高系统性能。
47 3
|
2月前
|
Linux查看内存命令
1. free free命令是最常用的查看内存使用情况的命令。它显示系统的总内存、已使用内存、空闲内存和交换内存的总量。 free -h • -h 选项:以易读的格式(如GB、MB)显示内存大小。 输出示例: total used free shared buff/cache available Mem: 15Gi 4.7Gi 4.1Gi 288Mi 6.6Gi 9.9Gi Swap: 2.0Gi 0B 2.0Gi • to
56 2
【YashanDB 知识库】kettle 同步大表提示 java 内存溢出
【问题分类】数据导入导出 【关键字】数据同步,kettle,数据迁移,java 内存溢出 【问题描述】kettle 同步大表提示 ERROR:could not create the java virtual machine! 【问题原因分析】java 内存溢出 【解决/规避方法】 ①增加 JVM 的堆内存大小。编辑 Spoon.bat,增加堆大小到 2GB,如: if "%PENTAHO_DI_JAVA_OPTIONS%"=="" set PENTAHO_DI_JAVA_OPTIONS="-Xms512m" "-Xmx512m" "-XX:MaxPermSize=256m" "-

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等