前言
不知道大家在开发过程中有没有遇到过类似的问题,明明通过JVM参数-Xmx4g设置了最大堆内存大小为4g,但是程序运行一段时间后发现占用的内存明显超过了8g,却并没有出现内存溢出等问题,那是什么东西占用了额外的内存空间呢?

一、背景
1.通过free -g查看服务器内存使用情况

2.通过ps查看java进程
项目启动命令为:

java -Xmx6g -Xms6g - -XX:+UseG1GC -jar /home/pgcp/pgcp-0.0.1-SNAPSHOT.jar

3.通过top命令查看资源使用情况

VIRT:virtual memory usage 虚拟内存
1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等
2、假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量

RES:resident memory usage 常驻内存
1、进程当前使用的内存大小,但不包括swap out
2、包含其他进程的共享
3、如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反
4、关于库占用内存的情况,它只统计加载的库文件所占内存大小

SHR:shared memory 共享内存
1、除了自身进程的共享内存,也包括其他进程的共享内存
2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小
3、计算某个进程所占的物理内存大小公式:RES – SHR
4、swap out后,它将会降下来

问题:
java项目启动通过Xmx6g设置了最大堆内存为6g,但是项目运行一段时间后发现,项目进行占用的内存飙升到了12g。
难道设置的JVM参数没有生效?内存是被什么东西吃掉了?

二、通过jmap查看JVM内存分配
jmap -heap 打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况.
命令:

jmap -heap 2196

发现JVM采用的G1垃圾回收器,堆内存的大小为6g。说明项目启动时设置的JVM参数是生效的。

三、通过NMT分析java进程的内存分配
1.什么是NMT
NMT的全称是Native Memory Tracker ,是一个本地内存跟踪工具。
常用来分析JVM的内存使用情况。

2.如何开启NMT
NMT功能默认关闭,可以通过以下方式开启:

-XX:NativeMemoryTracking=[off | summary | detail]

配置项 说明
off 默认配置
summary 只收集汇总信息
detail 收集每次调用的信息
注意,根据Java官方文档,开启NMT会有5%-10%的性能损耗;

如果想JVM退出时打印退出时的内存使用情况,可以通过如下配置项:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

采用如下命令重启项目:

java -Xmx8g -Xms8g - -XX:+UseG1GC -XX:NativeMemoryTracking=detail -jar /home/pgcp/pgcp-0.0.1-SNAPSHOT.jar

3.通过jcmd命令分析java进程的内存
首先通过jps找到对应的Java程序的pid,然后使用如下命令:

jcmd <pid> VM.native_memory

也可以通过一下命令指定内存单位,并将结果输出到文本中

jcmd 16696  VM.native_memory detail scale=MB >temp.txt

可以看到java进程的整个memory主要包含了Java Heap、Class、Thread、Code、GC、Internal、Symbol、Native Memory Tracking、unknown这几部分;其中reserved表示应用可用的内存大小,committed表示应用正在使用的内存大小。

分析:
可以看到,除了Java Heap占用了8g内存,Class和Internal也各占用了1g左右的内存。

JVM的内存
先放一张JVM的内存划分图,总体上可以分为堆和非堆(粗略划分,基于java8)


那么一个Java进程最大占用的物理内存为:

Max Memory = eden + survivor + old + String Constant Pool + Code cache + compressed class space + Metaspace + Thread stack(*thread num) + Direct + Mapped + JVM + Native Memory

堆和非堆内存
堆和非堆内存有以下几个概念:
init
表示JVM在启动时从操作系统申请内存管理的初始内存大小(以字节为单位)。JVM可能从操作系统请求额外的内存,也可以随着时间的推移向操作系统释放内存(经实际测试,这个内存并没有过主动释放)。这个init的值可能不会定义。

used
表示当前使用的内存量(以字节为单位)

committed
表示保证可供 Jvm使用的内存大小(以字节为单位)。 已提交内存的大小可能随时间而变化(增加或减少)。 JVM也可能向系统释放内存,导致已提交的内存可能小于 init,但是committed永远会大于等于used。

max
表示可用于内存管理的最大内存(以字节为单位)。

NMT的输出说明:


  1. Native Memory Tracking:

  2. Total: reserved=6988749KB, committed=3692013KB

  3.  堆内存

  4. - Java Heap (reserved=5242880KB, committed=3205008KB)

  5.  (mmap: reserved=5242880KB, committed=3205008KB)

  6.  类加载信息

  7. - Class (reserved=1114618KB, committed=74642KB)

  8.  (classes #10657)

  9.  (malloc=4602KB #32974)

  10.  (mmap: reserved=1110016KB, committed=70040KB)

  11.  线程栈

  12. - Thread (reserved=255213KB, committed=255213KB)

  13.  (thread #248)

  14.  (stack: reserved=253916KB, committed=253916KB)

  15.  (malloc=816KB #1242)

  16.  (arena=481KB #494)

  17.  代码缓存

  18. - Code (reserved=257475KB, committed=46551KB)

  19.  (malloc=7875KB #10417)

  20.  (mmap: reserved=249600KB, committed=38676KB)

  21.  垃圾回收

  22. - GC (reserved=31524KB, committed=23560KB)

  23.  (malloc=17180KB #2113)

  24.  (mmap: reserved=14344KB, committed=6380KB)

  25.  编译器

  26. - Compiler (reserved=598KB, committed=598KB)

  27.  (malloc=467KB #1305)

  28.  (arena=131KB #3)

  29.  内部

  30. - Internal (reserved=6142KB, committed=6142KB)

  31.  (malloc=6110KB #23691)

  32.  (mmap: reserved=32KB, committed=32KB)

  33.  符号

  34. - Symbol (reserved=11269KB, committed=11269KB)

  35.  (malloc=8544KB #89873)

  36.  (arena=2725KB #1)

  37.  nmt

  38. - Native Memory Tracking (reserved=2781KB, committed=2781KB)

  39.  (malloc=199KB #3036)

  40.  (tracking overhead=2582KB)

  41. - Arena Chunk (reserved=194KB, committed=194KB)

  42.  (malloc=194KB)

  43. - Unknown (reserved=66056KB, committed=66056KB)

  44.  (mmap: reserved=66056KB, committed=66056KB)

nmt返回结果中有reserved和committed两个值,这里解释一下:
reserved
reserved memory 是指JVM 通过mmaped PROT_NONE 申请的虚拟地址空间,在页表中已经存在了记录(entries),保证了其他进程不会被占用。

在堆内存下,就是xmx值,jvm申请的最大保留内存。

committed
committed memory 是JVM向操做系统实际分配的内存(malloc/mmap),mmaped PROT_READ | PROT_WRITE,相当于程序实际申请的可用内存。
在堆内存下,就是xms值,最小堆内存,heap committed memory。

注意,committed申请的内存并不是说直接占用了物理内存,由于操作系统的内存管理是惰性的,对于已申请的内存虽然会分配地址空间,但并不会直接占用物理内存,真正使用的时候才会映射到实际的物理内存。所以committed > res也是很可能的.

四、解决Internal内存占用过大问题
通过NMT的内存分析,我们已经定位到内存占用持续增长的原因,那么如果解决nternal内部内存持续增长,又不触发full gc的问题呢?
看看我们之前的启动的jvm参数:

-Xmx8g -Xms8g -XX:+UseG1GC  -XX:NativeMemoryTracking=detail

说明:
环境是jdk1.8,堆内存分配8g,采用G1垃圾回收器。

通过jmap查看内存分配:

发现最大堆内存MaxHeapSize和我们的启动参数设置的一致。
但是发现最大元数据空间内存非常大,这显然不是一个合适的值。

修改jvm参数:

-Xmx4g -Xms4g -Xmn2g   -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m -XX:+UseG1GC  -XX:NativeMemoryTracking=detail

注意:
-XX:PermSize=256m -XX:MaxPermSize=512m 这两个参数对于1.8就是过期的参数。
jdk1.8的元空间大小要通过参数-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m来控制。

修改后重启应用通过jmap查看内存分配:


JDK8里的元空间实际上使用的也是堆外内存,默认没有设置元空间大小的情况下,元空间最大堆外内存大小和Xmx是一致的。

这里需要额外注意的是:
永久代(JDK8的原生去,换成元数据空间)存放JVM运行时使用的类,永久代的对象在full GC时进行垃圾收集。

下面的图描述了从1.7到1.8,永久代的变更:


总结
本文主要介绍如何结合top,jmap,NMT工具对java进程的内存进行分析。
1、JDK1.8开始,自带的hostspot虚拟机取消了过去的永久区,而新增了metaspace区,从功能上看,metaspace可以认为和永久区类似,其最主要的功用也是存放类元数据,但实际的机制则有较大的不同。
2、对于JVM里面的内存需要在启动时进行限制,包括我们熟悉的堆内存,也要包括直接内存和元生区,这是保证线上服务正常运行最后的兜底。
3、重新熟悉java进程的内存组成。
java进程的内存组成 = heap + stack + metaspaceSize + directMemory
除了通过-Xmx4g -Xms4g参数控制程序启动的堆内存外,
不要忽视-Xss1024K控制每个stack的大小。
元空间限制:-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
直接内存使用限制:-XX:MaxDirectMemorySize=128m

设置Xmx参数大小的技巧:

根据文章得出:Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍;

于是,我们可以这么设置:

1.先设置比较大的Xmx,将项目部署到正式环境稳定运行一段时间

2.使用命令手动触发FullGC

jcmd [jvm的进程id]  GC.run

3.使用jstat -gc 或jmap -heap等命令查看堆内存,然后进行设置Xmx

JVM实际内存占用超过Xmx的原因,设置Xmx的技巧相关推荐

  1. java 超出 xmx_java进程内存占用超过xmx设置的问题

    今天通过top查看系统信息,发现系统启动的一个进程内存占用达900M之多 top - 11:30:26 up 167 days, 40 min,  1 user,  load average: 1.0 ...

  2. 内存占用过高的原因及解决方法

    写在正文前的废话: 这是我在csdn的第一篇文章,有点小小的兴奋~~~~ 至于为什么要写这篇文章,主要受自己深受这个问题的毒害,网上找了好多的帖子和方法,可能是我太菜了,不理解到底是为什么,怎么做的, ...

  3. 服务器内存占用导致死机原因

    最近,服务器经常死机. 1.第一步,登录服务器用top命令查看.内存占满了. 2.第二步,观察,一共16G的内存,内存占用,每天增加6G. 3.第三步,缓存区的内存占比很大. 用命令 free -g ...

  4. JVM 发生内存溢出的 8 种原因、及解决办法

    来源:http://t.cn/RAxJtbB 1. Java 堆空间 2. GC 开销超过限制 3. 请求的数组大小超过虚拟机限制 发生频率:2颗星 4. Perm gen 空间 5. Metaspa ...

  5. Linux buffer/cache 内存占用过高的原因以及解决办法

    表现现象 在Linux系统中,我们经常用free命令来查看系统内存的使用状态.在一个 CoreOS 的系统上,free命令的显示内容大概是这样一个状态: core@localhost ~ $ free ...

  6. 一次jvm导致线上内存占用过高问题定位

    背景:8G物理内存,8核CPU,jvm使用的G1垃圾回收器. 问题:线上内存占用告警,内存占用超过85%,且现象一直持续. 分析 看一下jvm启动参数配置: -Xms6144m -Xmx6144m - ...

  7. tomcat启动占了12g_windows server tomcat服务器部署内存占用高问题

    我的服务器的配置: # OS specific support.  $var _must_ be set to either true or false. JAVA_OPTS="-Xms10 ...

  8. jvm故障 内存和GC异常处理总结

    目录 排查流程 内存 堆内内存 unable to create new native thread Java heap space Meta space Stack Overflow request ...

  9. 一个神奇的bug:OOM?优雅终止线程?系统内存占用较高?

    摘要:该项目是DAYU平台的数据开发(DLF),数据开发中一个重要的功能就是ETL(数据清洗).ETL由源端到目的端,中间的业务逻辑一般由用户自己编写的SQL模板实现,velocity是其中涉及的一种 ...

最新文章

  1. Blender 3.0基础入门学习教程 Introduction to Blender 3.0
  2. GXGetImage方式连续采集和发送软触发采集
  3. GoEasy导入依赖的时候报错,包用不了,maven导包
  4. 数学基础加强1---机器学习与数学分析
  5. 【渝粤教育】国家开放大学2018年春季 0179-21T数据库基础与应用 参考试题
  6. 国内外流行的JS游戏开发框架介绍---kalrry
  7. android 脚本录制工具,安卓自动化脚本录制工具
  8. 用python生成个性二维码生成器_Python 生成个性二维码
  9. 电源技术——如何产生负电压
  10. 阿里巴巴 Excel工具easyExcel
  11. RRPP相切环配置示例
  12. 华为立体运维-第四课(APM探针部署)
  13. 推荐系统之FM与MF傻傻分不清楚
  14. ASP.NET图片添加水印
  15. 计算机网络实习什么,计算机网络实习目的
  16. Linux安装后连不上网络
  17. 按键精灵html代码,按键精灵键盘代码~
  18. php生鲜超市系统,毕业论文:基于PHP平台下的Ajax开发实践—网上生鲜超市系统的开发...
  19. python可执行文件的扩展名_可执行文件的扩展名是什么
  20. 地名翻译混乱 广东省质监局出台规范

热门文章

  1. 【Android自定义View实战】之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView
  2. 如何提高自己的自信心
  3. 2021年资料员-通用基础(资料员)考试及资料员-通用基础(资料员)考试报名
  4. 为虚构的游戏世界设计文字
  5. js 正则判断是中文,标点,英文符号,数字的 办法
  6. C语言字符排版,论坛编程大赛:字符行排版
  7. 为什么安卓手机总是越用越慢?
  8. php mail方法_php邮件发送的两种方式
  9. mybatis_动态SQL
  10. 字体,镑,像素之间的联系,留着做页面用