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

作者简介

刘韬,云和恩墨中间件服务交付团队专家

Java开发出身,10年WebLogic相关开发、运维工作经验,熟悉SOA、现代业务系统架构中各层组件,尤其擅长故障处理、性能优化等工作。

故障案例一

系统环境:

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

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

故障分析:

刚开始看到这个问题时,首先考虑可能是Native Memory Leak或JDK的Bug,然后看了下那些WebLogic进程的命令行参数:

/data/jdk1.6.0_45/bin/java -server -Xms2560m -Xmx2560m .....

从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版本大同小异)的内存区域为例展示一下:

(图片来源于SAP公司某技术专家在OOW演讲时的一篇文章)

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

这里发现一个规律,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)使用率上升。

而且,找到两篇讲的很清晰的文档了:

JAVA 进程在64位LINUX下占用巨大内存的分析

文章链接 :https://blog.chou.it/2014/03/java-consume-huge-memory-on-64bit-linux

Linux glibc >= 2.11 (RHEL 6) malloc may show excessive virtual memory usage

文章链接 :https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

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

这部分内容是有一定的原理的,和故障二里面的理论集中在一起,放在文章下方说明了。

解决办法:

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

export MALLOC_ARENA_MAX=4

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

后来我查到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):

替换为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]

#

......

故障分析:

由于这是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的使用和变化:

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

Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage

文章链接:https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

这篇文章里明确提到:

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内存的使用。

解决办法:

这次直接设置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.

RHSA-2012:0058 - Security Advisory

文章地址:https://access.redhat.com/errata/RHSA-2012:0058

Linux check M_ARENA_TEST, and M_ARENA_MAX ?

文章地址:https://www.zhihu.com/question/64733296

如果不考虑内存分配的性能,遇到这样的问题时,可使用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内存使用上的问题,详见:

IBM JDK虚拟内存使用量过高

Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage(文章开头部分)

链接地址:https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

Oracle JRockit虚拟机print_memusage输出的other内存使用过高

"Other" Allocation Reported By JRCMD print_memusage Is Too High And Causing An OutOfMemory Issue (Doc ID 2073773.1) (请在My Oracle Support站点搜索文章号)

原文发布于微信公众号 - 数据和云(OraNews)

原文发表时间:2017-10-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JackieZheng

让我头疼一下午的Excel合并单元格

在网上copy下模板代码,填充自己的业务数据,提供一个http接口基本就可以得到你要导出的数据了。

763
来自专栏happyJared

Mycat(入门篇)

Mycat是一款基于阿里开源产品Cobar而研发的开源数据库分库分表中间件(基于Java语言开发)。官网所言:Mycat国内最活跃的、性能最好的开源数据库中间件...

1102
来自专栏非著名程序员

分享一款值得分享的写作工具

俗话说的好:工欲善其事,必先利其器。作为技术的工匠来说,不仅仅需要好的开发工具,好的开发工具可以提高我们的工作效率,但是我们还需要更好的写作和总结工具才行,这样...

2236
来自专栏蘑菇先生的技术笔记

Redis系列(五)-Opserver的监控

2619
来自专栏青玉伏案

iOS开发之App间账号共享与SDK封装

上篇博客《iOS逆向工程之KeyChain与Snoop-it》中已经提到了,App间的数据共享可以使用KeyChian来实现。本篇博客就实战一下呢。开门见山,本...

2219
来自专栏刺客博客

ssr中多端口详解

1624
来自专栏码神联盟

碎片化 | 第四阶段-55-OpenSessionInViewFilter组件配置解决session问题-视频

如清晰度低,可转PC网页观看高清版本: http://v.qq.com/x/page/s05686to2z4.html OpenSessionInViewFi...

32411
来自专栏游戏杂谈

liunx下查看服务器硬件信息

今天安装了9台Linux服务器,型号完全不一样(有DELL、HP和IBM服务器),又懒得去对清单,如何在Linux下cpu的个数和核数呢?另外,nginx的cp...

562
来自专栏IT米粉

IntelliJ IDEA插件——冷门神器分享

IntelliJ IDEA就不必介绍了,至今还能保持IDE前三的神器,如今java程序员的首选,今天介绍几款冷门但绝对是神器的IDEA插件。 前言 IDEA自不...

3696
来自专栏信安之路

Windows下优雅地书写MarkDown

自从将博客搬到Hexo之后,书写MarkDown文档的频率就大大提高了,在享受着免排版的语法优势的同时又深深地受插入图片所困扰。找图床、加链接,大大降低了写文档...

990

扫码关注云+社区