前言

  在查看系统内存监控的过程中,发现有几台机器的内存使用率一直很高,而且是呈现一个不太正常的高度,初始以为是 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. Android 13 - Media框架(27)- ACodec(五)

    关注公众号免费阅读全文,进入音视频开发技术分享群! 前面几节我们了解了OMXNodeInstance是如何处理setPortMode.allocateBuffer.useBuffer的,这一节我们再回 ...

  2. 地图坐标转换 WGS84、BD09与GCJ02的相互转换

    高德地图 WGS84转GCJ02 export function wgs84ToGcj02(lng, lat) { if (out_of_china(lng, lat)) { return [lng, ...

  3. openstack neutron 报错

    openstack neutron /etc/neutron下面没有dhcp文件 查错发现安装时候打错

  4. Java中的包(Package)

    # 包(Package) **为方便管理类(按照不同的功能管理类),解决同名问题的发生** - 使用`package关键字`修饰包 - **类名(全类名)=包名(地址)+类名简称** ```java ...

  5. tomcat部署Jenkins

    安装环境 jdk 1.8 tomcat 9.0 jenkins 2.290 准备工作 安装好Tomcat,8080端口启动 安装好jdk,配置好环境变量 ECS服务器安全组放开8080端口 关闭防火墙 ...

  6. 机器学习决策树ID3算法,python实现代码

    机器学习决策树ID3算法,python实现代码 看到techflow介绍ID3算法,中间有代码示例.代码尝试执行力下,发现有错误. https://www.cnblogs.com/techflow/p ...

  7. Vue学习:16.组件通信

    组件通信就是指组件之间的数据传递.由于组件的数据是独立的,无法直接访问其他组件的数据,所以想要使用其他组件数据必须通过 组件通信! 在Vue.js中,组件之间的通信可以通过多种方式实现,包括 prop ...

  8. js获取指定日期的前一天/后一天

    date代表指定日期,格式:2018-09-27 day代表天数,-1代表前一天,1代表后一天 // date 代表指定的日期,格式:2018-09-27// day 传-1表始前一天,传1表始后一天 ...

  9. 590. N 叉树的后序遍历 | Javascript 递归实现

    题目 题目链接:590. N 叉树的后序遍历 解题思路 递归后续遍历,正常的思路 然后有一个要注意的地方就是如果js定义了全局变量来存储结果,每次调用函数之前一定要记得清空,否则答案会带上之前的结果. ...

  10. http请求方式-HttpClient 微信退款的接口,需要证书请求 https请求

    http请求方式-HttpClient import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import ...