前言

  在查看系统内存监控的过程中,发现有几台机器的内存使用率一直很高,而且是呈现一个不太正常的高度,初始以为是 GC 不完全,也就是 JVM 内有大量对象不能回收,于是采用 Arthas 诊断查看一下机器的 JVM 使用情况。

  这是挑选的一台机器查看的 JVM 使用情况,上图截图部分为内存使用情况。主要看第一个 HEAP-MEMORY-USAGE。这是 jvm 中堆的使用情况,init 为堆初始化 3.0 GiB,used 为已经使用了 598 MiB,committed 为已提交内存,即空闲内存+使用内存,但是一直被 JVM 占用,并没有归还给操作系统,这就造成了我们在监控上看的时候这台机器的内存占用率一直高居不下的主要原因之一。

按照正常业务理解,Jvm 在触发 GC 后回收的空闲内存应该会释放一部分给操作系统,但实际上并没有这么做,下面通过一段代码测试验证 CMS 和 G1 的物理内存归还机制。

测试

  在测试中,需要至少两个线程,一个用来不断的创建大对象,一个用来手动触发系统 GC。同时采用 JProfiler 监控 JVM 堆内存变化。

测试代码如下:

 1 import java.util.ArrayList;
2 import java.util.List;
3 ​
4 /**
5 * @Author: Li.Jincheng
6 * @Date: 2022/1/24 18:36
7 * @Description:
8 */
9 public class JvmMemoryTest {
10 static volatile List<BigObject> list = new ArrayList<>();
11 ​
12 public static void main(String[] args) {
13 int count = 512;
14 Thread createObjectThread = new Thread(() -> {
15 try {
16 for (int i = 1; i <= 10; i++) {
17 System.out.println(String.format("第%s次生产%s大小的对象", i, count));
18 add(list, count);
19 //休眠10秒
20 Thread.sleep(10000);
21 }
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 });
26 ​
27 Thread clearThread = new Thread(() -> {
28 while (true) {
29 if (list.size() < count) {
30 continue;
31 }
32 //当List内存到达512M,就通知GC
33 System.out.println("清理list.... 回收jvm内存....");
34 list.clear();
35 //GC
36 System.gc();
37 //打印堆内存信息
38 printJvmMemoryInfo();
39 }
40 });
41 ​
42 // 启动线程
43 createObjectThread.start();
44 clearThread.start();
45 ​
46 try {
47 Thread.currentThread().join();
48 } catch (InterruptedException e) {
49 e.printStackTrace();
50 }
51 }
52 ​
53 /**
54 * 打印Jvm内存情况
55 */
56 public static void printJvmMemoryInfo() {
57 //虚拟机级内存情况查询
58 int byteToMb = 1024 * 1024;
59 Runtime runtime = Runtime.getRuntime();
60 long vmTotal = runtime.totalMemory() / byteToMb;
61 long vmFree = runtime.freeMemory() / byteToMb;
62 long vmMax = runtime.maxMemory() / byteToMb;
63 long vmUse = vmTotal - vmFree;
64 System.out.println("##############");
65 System.out.println("JVM内存已用的空间为:" + vmUse + " MB");
66 System.out.println("JVM内存的空闲空间为:" + vmFree + " MB");
67 System.out.println("JVM总内存空间为:" + vmTotal + " MB");
68 System.out.println("JVM总内存最大堆空间为:" + vmMax + " MB");
69 System.out.println("##############");
70 }
71 ​
72 /**
73 * 创建大对象列表
74 *
75 * @param list
76 * @param count
77 */
78 public static void add(List<BigObject> list, int count) {
79 for (int i = 0; i < count; i++) {
80 BigObject bigObject = new BigObject();
81 list.add(bigObject);
82 try {
83 Thread.sleep(50);
84 } catch (InterruptedException e) {
85 e.printStackTrace();
86 }
87 }
88 }
89 ​
90 public static class BigObject {
91 //生成5M的对象
92 private byte[] bytes = new byte[1024 * 1024 * 5];
93 }
94 }

CMS 垃圾回收器

配置:

-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC

结果:

  如上图所示,蓝色部分为实际使用内存,绿色部分为空闲内存,从图上可以看出在开始的时候即使触发了 GC 操作,JVM 回收了内存,但是并没有立即将空闲内存归还操作系统。在第三次 gc 后,可以明显看到 JVM 的空闲空间明显下降,这表明 JVM 已经归还了一部分内存,后面,随着 GC 次数增加慢慢的将内存归还。也就是说,CMS 垃圾回收器最终也会将申请的内存归还操作系统。

G1 垃圾回收器

配置:

-Xms128M -Xmx2048M -XX:+UseG1GC

结果:

  从上图可以看出,每触发一次 GC,JVM 的使用内存和空闲内存总和都降到了初始值 128M,也就是说在使用 G1 垃圾回收器时,每次 GC 都会将 JVM 新申请开辟的空间归还给操作系统。这也是一开始我们理解的垃圾回收机制以及预期结果。

总结

  CMS 垃圾回收器,在 JVM 申请内存后,会随着 GC 次数增加和频率足见拉长,从继续申请内存到慢慢归还给操作系统,知道如图所示出现一次全部归还后,每一次的 GC 都会将剩余空间归还操作系统;

  G1 垃圾回收器与之相反,每次 GC 后都会将内存全部归还操作系统,大大降低了机器的内存占用。

垃圾回收器比较:CMS 和 G1的更多相关文章

  1. [JVM 相关] Java 新型垃圾回收器(Garbage First,G1)

    回顾传统垃圾回收器 HotSpot 垃圾收集器实现 Serial Collector(串型收集器) 使用场景,大多数服务器是单核CPU. 适用收集场景:1. 新生代收集(Young Generatio ...

  2. 垃圾回收之CMS、G1、ZGC对比

    ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的大小,或者活跃对象的大小而增加: ...

  3. 探索ParNew和CMS垃圾回收器

    前言 上篇文章我们一起分析了JVM的垃圾回收机制,了解了新生代的内存模型,老年代的空间分配担保原则,并简单的介绍了几种垃圾回收器.详细内容小伙伴们可以去看一下我的上篇文章:秒懂JVM的垃圾回收机制. ...

  4. G1垃圾回收器在并发场景调优

    一.序言 目前企业级主流使用的Java版本是8,垃圾回收器支持手动修改为G1,G1垃圾回收器是Java 11的默认设置,因此G1垃圾回收器可以用很长时间,现阶段垃圾回收器优化意味着针对G1垃圾回收器优 ...

  5. Java GC系列(3):垃圾回收器种类

    本文由 ImportNew - 好好先生 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 在这篇教程中我们将学习几种现有的垃圾回收器.在 ...

  6. [译]Java垃圾回收器的类型

    说明:这篇文章来翻译来自于Javapapers 的Types of Java Garbage Collectors 在这部分的教程中我们将讲到可使用的四种不同类型的Java垃圾回收器.垃圾回收是Jav ...

  7. Hotspot JVM垃圾回收器

    前两篇<JVM入门——运行时数据区><JVM常见垃圾回收算法>所提到的实际上JVM规范以及常用的垃圾回收算法,具体的JVM实现实际上不止一种,有JRockit.J9等待,当然最 ...

  8. 一篇文章让你了解GC垃圾回收器

    简单了解GC垃圾回收器 了解GC之前我们首先要了解GC是要做什么的?顾名思义回收垃圾,什么是垃圾呢? GC回收的垃圾主要指的是回收堆内存中的垃圾对象. 从根对象出发,所有被引用的对象,都是存活对象 其 ...

  9. JVM 专题二十:垃圾回收(四)垃圾回收器 (一)

    1. GC分类与性能指标 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商.不同版本的JVM来实现.由于JDK的版本处于高速迭代过程中,因此Java发展至今已经产生了众多的GC版本.从不同角度分 ...

  10. 如何选择JVM垃圾回收器?

    明确垃圾回收器组合 -XX:+UseSerialGC 年轻代和老年代都用串行收集器 -XX:+UseParNewGC 年轻代使用ParNew,老年代使用 Serial Old -XX:+UsePara ...

随机推荐

  1. go 交叉编译遇到的错误, 有路由方法却找不到。

    panic: 'OrderCancel' method doesn't exist in the controller Controller今天线下能正常编译,到线上却panic了.发现是自己导入了i ...

  2. Uni-app极速入门(一) - 第一个小程序

    Uni-app 介绍 官网:https://www.dcloud.io/index.html uni-app是为js开发者提供的一个全端开发框架,可以开发一次编译为web.App.小程序(微信/阿里/ ...

  3. conda错误 创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the above report.

    创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the ab ...

  4. Hugging Face x LangChain: 全新 LangChain 合作伙伴包

    我们很高兴官宣发布 langchain_huggingface ,这是一个由 Hugging Face 和 LangChain 共同维护的 LangChain 合作伙伴包.这个新的 Python 包旨 ...

  5. ros2 foxy订阅话题问题

    代码片段 这部分代码在galactic版本编译是OK的,可在foxy下编译就出了问题 TeleopPanel::TeleopPanel(QWidget* parent) : rviz_common:: ...

  6. 8.10考试总结(NOIP模拟35)[玩游戏·排列·最短路·矩形]

    所谓人,无论是谁到了最后,都会形单影只. T1 玩游戏 解题思路 可以把序列从 k 位置掰成两个序列. 问题就变成了两个序列从开头走向末尾是否可以保证前缀和之和一直不大于 0 . 并且可以移动到两个序 ...

  7. windows报错

    如果说你dns没有权威的话1.先去long.com上面右键属性把"区域传送给所有服务器打勾"2.右键属性,在名称分析器中,输入要添加为辅助dns的服务器的ip显示解析成功就可以了

  8. Java异常中throw 与throws的区别

    throw 与 throws区别 在Java中,throws和throw是两个不同的关键字,它们在异常处理中起着不同的作用. throws关键字: throws用于声明一个方法可能会抛出的异常.当一个 ...

  9. webpack代码分割

    在做一些单页应用中,若不做任何处理,所有项目文件会打包为一个文件,这个文件非常的大,造成网页在首次进入时比较缓慢.做了代码分割后,会将代码分离到不同的chunk中,然后进行按需加载这些文件,能够提高页 ...

  10. java 中 pop 和 peek 方法区别

    相同点:都返回栈顶的值. 不同点:peek 不改变栈的值(不删除栈顶的值),pop会把栈顶的值删除. 下面通过代码展现 /* * 文 件 名: TestPeekAndPopDiff.java */ i ...