关于jvm:jvm疯狂吞占内存罪魁祸首是谁

51次阅读

共计 2419 个字符,预计需要花费 7 分钟才能阅读完成。

导读

JVM 是 Java Virtual Machine 的缩写,中文名为 Java 虚拟机。它是一种用于计算设施的标准,是一个虚构进去的计算机,次要通过在理论的计算机上仿真模仿各种计算机性能来实现的。在理论使用过程中,易观技术人员留神到一台开发机上各个微服务过程占用内存很高,随即便开展了考察 ……

景象:前段时间发现某台开发机上各个微服务过程占用内存很高,这里记录下解决思路,仅供参考。

  • Centos6.10+Jdk1.8+SpringBoot1.4.4 环境下各个 JVM 过程所占内存应用状况

VIRT 和 RES 都很高 ……

以其中某个过程为例(过程启动日期为 8 月 9 日,排查时候的工夫是 8 月 10 日 10:54:58,也就是说该过程运行工夫应该不会超过 48 小时)

top 命令查看该过程占用内存状况 (能够看到此过程曾经占用 2.7G 物理内存)

为了排除掉是因为中途有压力测试的嫌疑,将此服务进行了重启,然而刚起的过程 (19146),

占内存状况 RES:1.8G,  VIRT:33.4G  …

JVM 过程动不动就是 2G 以上的内存,然而开发环境并没什么业务申请,谁是罪魁祸首?

解决问题之前,先温习下几个基础知识。

1. 什么是 RES 和 VIRT?

  • RES:resident memory usage 常驻内存

(1)过程以后应用的内存大小,但不包含 swap out

(2)蕴含其余过程的共享

(3)如果申请 100m 的内存,理论应用 10m,它只增长 10m,与 VIRT 相同

(4)对于库占用内存的状况,它只统计加载的库文件所占内存大小

RES = CODE + DATA

  • VIRT:virtual memory usage

(1)过程“须要的”虚拟内存大小,包含过程应用的库、代码、数据等

(2)如果过程申请 100m 的内存,但理论只应用了 10m,那么它会增长 100m,而不是理论的使用量

VIRT = SWAP + RES

2. Linux 与过程内存模型

3. JVM 内存模型(1.7 与 1.8 之间的区别)

所以 JVM 过程内存大小大抵为:

非 heap(非 heap= 元空间 + 栈内存 +…)+heap+JVM 过程运行所需内存 + 其余数据

那么会是 jvm 内存透露引起的吗?

  • 应用 Jmap 命令将整个 heap dump 下来,而后用 jvisualvm 剖析

能够看到,堆内存一切正常(dump 会引起 FGC, 但并不影响此论断)

那么可能是 SpringBoot 的起因吗?

为了验证此问题,通过部署零碎在开发机上起了 1 个没有任何业务代码的 springboot 过程,仅仅是引入注册核心

查看此过程内存占用状况:

显著曾经设置了 Xmx 为 512MB,尽管 Xmx 不等于最终 JVM 所占总内存,但至多也不会偏差太多;那么应用 jmap 命令查看以后 jvm 堆内存配置和应用状况(上面的图 2 是在图 1 现场 5 分钟之后截取的)

**(图 1)
**

(图 2)

所以从 2 次的 jmap 后果中,能够得出以下几个论断:

  • 咱们的 Xmx 设置并没有失效,因为 MaxHeapSize≠Xmx
  • 图 1 中 jvm 占用内存计算:

元空间 (20.79MB)+ eden(834MB)+ 年轻代 (21MB)+ 线程栈(38*1024KB)+JVM 过程自身运行内存 + NIO 的 DirectBuffer +JIT+JNI+…≈top(Res) 1.1G

以后 jvm 线程数统计:jstack 7311 |grep‘tid’|wc –l  (linux 64 位零碎中 jvm 线程默认栈大小为 1MB)

  • Eden 区进行了屡次扩容,由图 1 可知 eden 区可用空间曾经不够用了(容量:843MB,已应用:834MB),图 2 中扩容到 1566MB
  • Eden 区经验了 Minor Gc,由图 2 可知 eden 区已应用空间:60MB,阐明之前在 eden 区的对象大部分曾经被回收,局部未被回收的对象曾经转入到扩大 1 区了

Xmx 设置为何未失效?

查看部署零碎的启动脚本,发现启动形式为:Java –jar $jar_file –Xms512m –Xmx1024m

正确的 Java 命令:

java [options] class [arguments]

java [options] -jar file.jar [arguments]

其实到这里,也找到了此问题起因所在,Java –jar $jar_file –Xms512m –Xmx1024m 被 JVM 解释成了程序的参数。

手动执行:    java –Xms512m –Xmx1024m –jar ems-client-1.0.jar

至此,RES 过高的问题已解决,然而 VIRT 的问题还在

应用系统命令 pmap -x 3516 查看过程的内存映射状况,会发现大量的 64MB 内存块存在;统计了下,大略有 50 多个 65404+132=65536, 正好是 64MB,算起来大概 3 个多 G

于是 Google 之,发现大抵的起因是从 glibc2.11 版本开始,linux 为了解决多线程下内存调配竞争而引起的性能问题,加强了动态内存调配行为,应用了一种叫做 arena 的 memory pool, 在 64 位零碎上面缺省配置是一个 arena 大小为 64M,一个过程能够最多有 cpu cores 8 个 arena。假如机器是 8 核的,那么最多能够有 8 8 = 64 个 arena,也就是会应用 64 * 64 = 4096M 内存。

然而咱们能够通过设置零碎环境变量来扭转 arena 的数量:

export MALLOC_ARENA_MAX=8(个别倡议配置程序 cpu 核数)

配置环境变量使其失效,再重启该 jvm 过程,VIRT 比之前少了快 2 个 G:

/ 具体的参考资料 /

https://access.redhat.com/doc…

https://code.woboq.org/usersp…

总结: 这里只是提供一种解决问题的思路,仅供参考;个别咱们遇到问题之后,首先想到的是不是程序有问题,而后跟踪了很久还是未找到问题根本原因;几经周折,才发现问题是呈现在最容易被咱们漠视的中央(比方这里的脚本命令问题)!当然,每个公司或者每个人遇到的问题都不太一样,须要具体问题具体分析。

正文完
 0