前言

  在查看系统内存监控的过程中,发现有几台机器的内存使用率一直很高,而且是呈现一个不太正常的高度,初始以为是 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. ubuntu 虚拟机安装完docker 以后 出现tls时遇到的坑

    网上很多都是更改镜像源,发现更改以后还是不行.请更改网路模式为桥接模式就ok了.

  2. ubuntu安装之后要做的10件事

    部分内容整理自网络,如果侵权还请联系 基础配置 换源 换源 [ubuntu清华源镜像站] ctrl+click,进入镜像站链接,选择合适的版本,将镜像地址粘贴到本地文件里,对于: <24.04的 ...

  3. HMI-Board上手指南

    介绍 HMI-Board为 RT-Thread 联合瑞萨推出的高性价比图形评估套件,取代传统的 HMI+主控板硬件,一套硬件即可实现 HMI+IoT+控制的全套能力.依托于瑞萨高性能芯片 RA6M3 ...

  4. 8.21考试总结(NOIP模拟45)[打表·蛇·购物·ants]

    有型的东西终究会消逝,不过--终于,这份回忆还是永远不朽的- 前言 这次考试暴露出来了不少问题. 比如答题策略策略不当导致 T2 的 65pts 暴力根本没有打. 知识遗忘太快不牢固,T4 是之前的一 ...

  5. 01.Alpine编译glibc

    概要 本文档采用glibc2.28版本作为示例,模拟内网环境无法访问github等开源社区 为精简docker容器镜像,采用Alpine镜像,需要手动编译glibc源代码 制作编译好的glibc二进制 ...

  6. python相关常见安装问题

    1 Centos7安装pip 参考链接:centos7 pip升级 - fuhaizi - 博客园 (cnblogs.com) Centos7默认pip版本: 使用默认pip版本安装numpy库,会报 ...

  7. 对pta的总结_1

    前言 这三次pta难度在不断上升的同时,要求我们线上慕课+自主学习来了解更多的java中的各种方法,如:正则表达式 List Map等.与此同时要求我们展开尝试并熟练类的构造,类的引用,链表的基本运用 ...

  8. INFINI Console 与华为鲲鹏完成产品兼容互认证

    何为华为鲲鹏认证 华为鲲鹏认证是华为云围绕鲲鹏云服务(含公有云.私有云.混合云.桌面云)推出的一项合作伙伴计划,旨在为构建持续发展.合作共赢的鲲鹏生态圈,通过整合华为的技术.品牌资源,与合作伙伴共享商 ...

  9. C++中 符号的优先级

    符号 运算顺序 :: 从左至右 a++ a-- type() type{} a() a[] . -> 从左至右 ! ~ ++a --a +a -a (type) sizeof &a *a ...

  10. 手把手教你搭建Docker私有仓库Harbor

    1.什么是Docker私有仓库 Docker私有仓库是用于存储和管理Docker镜像的私有存储库.Docker默认会有一个公共的仓库Docker Hub,而与Docker Hub不同,私有仓库是受限访 ...