作者简介

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

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站点搜索文章号)

原文引用地址:

当Java虚拟机遇上Linux Arena内存池相关推荐

  1. Arena内存池简介

    一. 什么是内存池? 内存池(Memory Pool)是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation). 通常我们习惯直接使用new.mallo ...

  2. leveldb:Arena内存池

    leveldb : Arena内存池 Arena Arena内存管理模型 Arena的构造与析构实现 Arena提供的接口 Allocate AllocateFallback AllocateNewB ...

  3. linux boost内存池,开源C++函数库Boost内存池使用与测试

    [IT168 专稿]Boost库是一个可移植的开源C++函数库,鉴于STL(标准模板库)已经成为C++语言的一个组成部分,可以毫不夸张的说,Boost是目前影响最大的通用C++库.Boost库由C++ ...

  4. (转载)简单linux C++内存池

    C++代码 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ...

  5. linux boost内存池,C++ boost库教程之内存池

    Boost  Pool 库提供了一个内存池分配器,它是一个工具,用于管理在一个独立的.大的分配空间里的动 态内存.当你需要分配和回收许多不的对象或需要更高效的内存控制时,使用内存池是一个好的 解决方案 ...

  6. 经典的arena内存池实现-levelDB的内存池实现

    Arena实现 arena可以说是解决内存碎片的利器,虽然有很多前辈说,要相信malloc的实现,你能想到的那些问题在设计Malloc的时候肯定都考虑到了.是的你可以相信malloc的实现,但是你不能 ...

  7. 占内存少的java开发工具_Java所占内存中神奇的64MB

    一 前言 在生产环境,Java应用程序设置了最大JVM内存后,经常发现实际使用的内存,可能超过设置的JVM最大内存数jmap -heap pid 通过这个命令可以方便查看java的内存分配情况.一般情 ...

  8. 深入理解Java虚拟机学习笔记-1.JVM内存模型

    JVM内存模型 1.内存模型结构图 名称 特征 作用 配置参数 异常 程序计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程相同,使 ...

  9. java 西部数码_西部数码Java虚拟主机功能升级

    西部数码Java虚拟主机功能升级: 西部数码Java虚拟主机新增内存使用查看功能和自动重启Tomcat功能,西部数码新增这两个功能目的是方便java主机用户掌握内存状况,并可以设置一定的频率自动重启t ...

最新文章

  1. Android客户端捕获http请求包的方法
  2. MySQL数据库(八) 一一 数据库信息和使用序列
  3. postgresql 9.1 暂停 stream 后使用 rsync 异机同步文件
  4. Git复习(七)之自定义git、忽略特殊文件、配置文件
  5. Linux:环境变量
  6. python读取与写入json+csv变成coco的json文件+安装labelme
  7. 树莓派视频监控 —— 使用 mjpg
  8. XenApp / XenDesktop 7.6 初体验一   安装, 配置站点和序列号服务器
  9. android面试题之二(红黑联盟)
  10. 【LeetCode】马三来刷题之 Reverse Vowels of a Stringm
  11. 论“无常,苦、无我”
  12. HTML+CSS项目练习(8)-发光文字动画
  13. Zabbix proxy
  14. 微信小程序开发语言(微信小程序开发教程)详细步骤
  15. SCAU-春季训练-不应该啊(怎么这么菜。。。)
  16. 一文科普区块链技术:未来注定将颠…
  17. 72---百钱买百鸡问题
  18. 单目标多目标优化算法的测试函数与解
  19. SpringBoot 集成接口文档,老鸟们也被打脸了!
  20. 理解 Thread.Sleep 函数

热门文章

  1. ArrayList源码分析
  2. 机械革命无法使用U盘启动linux,机械革命bios设置,详细教您机械革命bios怎么设置u盘启动...
  3. Win10系统C盘之前还有20G空间突然红了爆满如何解决
  4. MySQL学习(三) 数据类型约束、TCL语言、流程控制
  5. 没钱不能创业,教写商业计划书
  6. 猫眼数据SQLITE保存格式
  7. 开放大世界 codelikeme
  8. 高职计算机自主招生面试题,高职自主招生面试题
  9. 《游戏学习》纯JS中国象棋人机对战html游戏源码
  10. python pos函数_如何用Python画一只肥肥的柯基狗狗—turtle库绘制椭圆与弧线实践