先点赞再看,养成好习惯

某天,运维老哥突然找我:“你们的某 JAVA 服务内存占用太高,告警了!GC 后也没释放,内存只增不减,是不是内存泄漏了!”

然后我赶紧看了下监控,一切正常,距离上次发版好几天了,FULL GC 一次没有,YoungGC,十分钟一次,堆空闲也很充足。

运维:“你们这个服务现在堆内存 used 才 800M,但这个 JAVA 进程已经占了 6G 内存了,是不是你们程序出啥内存泄露的 bug 了!”

我想都没想,直接回了一句:“不可能,我们服务非常稳定,不会有这种问题!”

不过说完之后,内心还是自我质疑了一下:会不会真有什么bug?难道是堆外泄露?线程没销毁?导致内存泄露了???

然后我很“镇定”的补了一句:“我先上服务器看看啥情况”,被打脸可就不好了,还是不要装太满的好……

迅速上登上服务器又仔细的查看了各种指标,Heap/GC/Thread/Process 之类的,发现一切正常,并没有什么“泄漏”的迹象。

和运维的“沟通”

我们这个服务很正常啊,各个指标都ok,什么内存只增不减,在哪呢

运维:你看你们这个 JAVA 服务,堆现在 used 才 400MB,但这个进程现在内存占用都 6G 了,还说没问题?肯定是内存泄露了,锅接好,赶紧回去查问题吧

然后我指着监控信息,让运维看:“大哥你看这监控历史,堆内存是达到过 6G 的,只是后面 GC 了,没问题啊!”

运维:“回收了你这内存也没释放啊,你看这个进程 Res 还是 6G,肯定有问题啊”

我心想这运维怕不是个der,JVM GC 回收和进程内存又不是一回事,不过还是和得他解释一下,不然一直baba个没完

“JVM 的垃圾回收,只是一个逻辑上的回收,回收的只是 JVM 申请的那一块逻辑堆区域,将数据标记为空闲之类的操作,不是调用 free 将内存归还给操作系统”

运维顿了两秒后,突然脸色一转,开始笑起来:“咳咳,我可能没注意这个。你再给我讲讲 JVM 的这个内存管理/回收和进程上内存的关系呗”

虽然我内心是拒绝的,但得罪谁也不能得罪运维啊,想想还是给大哥解释解释,“增进下感情”

操作系统 与 JVM的内存分配

JVM 的自动内存管理,其实只是先向操作系统申请了一大块内存,然后自己在这块已申请的内存区域中进行“自动内存管理”。JAVA 中的对象在创建前,会先从这块申请的一大块内存中划分出一部分来给这个对象使用,在 GC 时也只是这个对象所处的内存区域数据清空,标记为空闲而已

运维:“原来是这样,那按你的意思,JVM 就不会将 GC 回收后的空闲内存还给操作系统了吗?”

为什么不把内存归还给操作系统?

JVM 还是会归还内存给操作系统的,只是因为这个代价比较大,所以不会轻易进行。而且不同垃圾回收器 的内存分配算法不同,归还内存的代价也不同。

比如在清除算法(sweep)中,是通过空闲链表(free-list)算法来分配内存的。简单的说就是将已申请的大块内存区域分为 N 个小区域,将这些区域同链表的结构组织起来,就像这样:

每个 data 区域可以容纳 N 个对象,那么当一次 GC 后,某些对象会被回收,可是此时这个 data 区域中还有其他存活的对象,如果想将整个 data 区域释放那是肯定不行的。

所以这个归还内存给操作系统的操作并没有那么简单,执行起来代价过高,JVM 自然不会在每次 GC 后都进行内存的归还。

怎么归还?

虽然代价高,但 JVM 还是提供了这个归还内存的功能。JVM 提供了-XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio 两个参数,用于配置这个归还策略。

  • MinHeapFreeRatio 代表当空闲区域大小下降到该值时,会进行扩容,扩容的上限为 Xmx
  • MaxHeapFreeRatio 代表当空闲区域超过该值时,会进行“缩容”,缩容的下限为Xms

不过虽然有这个归还的功能,不过因为这个代价比较昂贵,所以 JVM 在归还的时候,是线性递增归还的,并不是一次全部归还。

但是但是但是,经过实测,这个归还内存的机制,在不同的垃圾回收器,甚至不同的 JDK 版本中还不一样!

不同版本&垃圾回收器下的表现不同

下面是我之前跑过的测试结果:

public static void main(String[] args) throws IOException, InterruptedException {List<Object> dataList = new ArrayList<>();for (int i = 0; i < 25; i++) {byte[] data = createData(1024 * 1024 * 40);// 40 MBdataList.add(data);}Thread.sleep(10000);dataList = null; // 待会 GC 直接回收for (int i = 0; i < 100; i++) {// 测试多次 GCSystem.gc();Thread.sleep(1000);}System.in.read();
}
public static byte[] createData(int size){byte[] data = new byte[size];for (int i = 0; i < size; i++) {data[i] = Byte.MAX_VALUE;}return data;
}
JAVA 版本 垃圾回收器 VM Options 是否可以“归还”
JAVA 8 UseParallelGC(ParallerGC + ParallerOld) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40
JAVA 8 CMS+ParNew -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
JAVA 8 UseG1GC(G1) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseG1GC
JAVA 11 UseG1GC(G1) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40
JAVA 16 UseZGC(ZGC) -Xms100M -Xmx2G -XX:MaxHeapFreeRatio=40 -XX:+UseZGC

测试结果刷新了我的认知。,MaxHeapFreeRatio 这个参数好像并没有什么用,无论我是配置40,还是配置90,回收的比例都有和实际的结果都有很大差距。

但是文档中,可不是这么说的……

而且 ZGC 的结果也是挺意外的,JEP 351 提到了 ZGC 会将未使用的内存释放,但测试结果里并没有。

除了以上测试结果,stackoverflow 上还有一些其他的说法,我就没有再一一测试了

  1. JAVA 9 后-XX:-ShrinkHeapInSteps参数,可以让 JVM 已非线性递增的方式归还内存
  2. JAVA 12 后的 G1,再应用空闲时,可以自动的归还内存

所以,官方文档的说法,也只能当作一个参考,JVM 并没有过多的透露这个实现细节。

不过这个是否归还的机制,除了这位“热情”的运维老哥,一般人也不太会去关心,巴不得 JVM 多用点内存,少 GC 几回……

而且别说空闲自动归还了,我们希望的是一启动就分配个最大内存,避免它运行中扩容影响服务;所以一般 JAVA 程序还会将 XmsXmx配置为相等的大小,避免这个扩容的操作。

听到这里,运维老哥若有所思的说到:“那是不是只要我把 Xms 和 Xmx 配置成一样的大小,这个 JAVA 进程一启动就会占用这个大小的内存呢?”

我接着答到:“不会的,哪怕你 Xms6G,启动也只会占用实际写入的内存,大概率达不到 6G,这里还涉及一个操作系统内存分配的小知识”

Xms6G,为什么启动之后 used 才 200M?

进程在申请内存时,并不是直接分配物理内存的,而是分配一块虚拟空间,到真正堆这块虚拟空间写入数据时才会通过缺页异常(Page Fault)处理机制分配物理内存,也就是我们看到的进程 Res 指标。

可以简单的认为操作系统的内存分配是“惰性”的,分配并不会发生实际的占用,有数据写入时才会发生内存占用,影响 Res。

所以,哪怕配置了Xms6G,启动后也不会直接占用 6G 内存,实际占用的内存取决于你有没有往这 6G 内存区域中写数据的。

运维:“卧槽,还有惰性分配这种东西!长知识了”

我:“这下明白了吧,这个内存情况是正常的,我们的服务一点问题都没有”

运维:“

运维:你们 JAVA 服务内存占用太高,还只增不减!告警了,快来接锅相关推荐

  1. java服务内存占用过高

    文章目录 1.查看内存占用高的进程 2.查看对象个数和占用内存大小 3.导出内存镜像 4.安装独立版本的Memory Analyzer工具 5.使用MAT工具进行分析 6.注意 以下代码没有特殊说明, ...

  2. 如何占用计算机内存,电脑内存占用太高怎么办 教你电脑内存不够用解决方法...

    现在很多白领买电脑,都会优先考虑电脑的续航和便捷,因为他们买电脑主要也就办办公,看看电影,又不玩大型游戏,所以高性能对他们来说也没什么必要. 笔记本电脑 但现在市面上续航久,便携性比较高的Intel电 ...

  3. 服务器内存占用太高如何解决及知识点介绍

    背景 服务器内存占用太高 查看内存 free -m 查看服务器内存使用情况,-m的意思是内存显示单位是mb top 查看服务器所有进程占用内存,为了查看方便,可以使用 shift+md可以让进程按照内 ...

  4. 怎么减少计算机内存占有,还在为电脑内存占用太高而烦恼吗?教你一招轻松解决...

    查毒了也不管用.结果是电脑的物理内存占用太高了,下面就教大家怎么减少物理内存,希望对您有所帮助! 1.启动"windows任务管理器",快捷键"alt+ctrl+dele ...

  5. UE 手游在 iOS 平台运行时内存占用太高?试试这样着手优化

    性能优化,对游戏开发来说是一个需要不断钻研的课题,性能越好,游戏才会运行的更加顺畅,玩家的体验感才会更好.腾讯游戏学院专家.游戏客户端开发 Leonn,将和大家分享 UE 手游在 iOS 平台上的内存 ...

  6. java程序内存占用过高问题排查

    一.现象 收到线上机器报警(内存使用过高),对报警的机器节点重启后恢复正常,搁天后新的节点又开始报警: 二.排查 直接对线上机器执行dump命令,由于线上机器还有流量在持续请求,因此dump时间比较长 ...

  7. linux服务器内存占用太高-释放内存

    修改/proc/sys/vm/drop_caches,释放Slab占用的cache内存空间(参考drop_caches的官方文档): Writing to this will cause the ke ...

  8. 如何清理占用计算机内存,电脑内存占用太高怎么办?教你一招轻松解决~-怎么清理电脑内存...

    大概在三年前,让我选择一台电脑,我第一看重的应该是这台电脑的性能,但如果是现在,我可能更注重电脑的续航和便捷,其实不止是我,现在很多人在选择电脑时也应该越来越倾向于选择一台高续航且便捷的,而不是高性能 ...

  9. linux缓冲区内容占用较多什么原因,Linux中Cache内存占用太高解决办法

    在Linux系统中,咱们常常用free命令来查看系统内存的使用状态.在一个RHEL6的系统上,free命令的显示内容大概是这样一个状态:html 这里的默认显示单位是kb,个人服务器是128G内存,因 ...

最新文章

  1. UVA 11020 - Efficient Solutions(set)
  2. Sublime Text 3 MarkdownEditing布局设置
  3. Windows驱动开发学习笔记(三)—— 内核空间内核模块
  4. hive数据类型转换
  5. 熬夜所带来的伤害,远比我们想象的更可怕
  6. Java中使用log4j记录日志
  7. 一、创建Assetbundle 在unity3d开发的游戏中,无论模型,音频,还是图片等,我们都做成Prefab,然后打包成Assetbundle,方便我们后面的使用,来达到资源的更新。
  8. Chrome用户不喜新版:宁用其他浏览器也不要用旧版本
  9. 天联高级版客户端_壹拓网科技关于金万维天联标准版、异速联和天联高级版区别的讲解...
  10. Mac安装redis与后台启动
  11. 协程入门(一):启动与挂起
  12. 两道关于回溯法,分支限界法的算法题
  13. 放射性核废料处理matlab模型,放射性废物处理问题
  14. 案例研究 路由器到路由器EOMPLS---基于端口
  15. Hibernate3动态条件查询
  16. C_北理工乐学_结构
  17. uva 10066 LCS
  18. Python3 flags
  19. 一个屌丝程序猿的人生(八十六)
  20. MIPI接口资料汇总(精)

热门文章

  1. ArcGIS Server10.2安装教程(2022最新版)
  2. bilibili获取cookie
  3. SVN版本管理与代码上线架构方案
  4. 电商数据分析方法——搭建数据指标体系
  5. define(defined是什么意思)
  6. SQLScout——AndroidStudio插件(Sqlite神器)
  7. 基于Html的四季春茶销售网设计与实现
  8. Linux的FHS标准是什么意思?
  9. Php怎么转成word,怎么把word转换成ppt
  10. 「模型即服务AI」1分钟调用SOTA人脸检测,同时搭建时光相册小应用