JVM调优之探索CMS和G1的物理内存归还机制
前言:
公司有一个资产统计系统,使用频率很低,但是要求在使用时查询速度快,因此想到做一些缓存放在内存中,在长时间没有使用,持久化到磁盘中,并对垃圾进行回收,归还物理内存给操作系统,从而节省宝贵资源给其它业务系统。当我做好缓存时,却发现了一个棘手的问题,通过程序释放资源并通知GC回收资源后,堆内存的已用内存减少了,空闲内存增加了,可是进程占用系统内存却没有减少。查阅了很多资料,也尝试过很多次,都没有完美解决问题。直到后来看到一段评论谈及G1垃圾回收器,才恍然大悟。
接下来,通过一个小demo给大家演示一下两种垃圾回收器对物理内存归还的区别。如果有什么不对的地方,希望大家能够在评论里面指正。
- 堆大小配置:
-Xms128M -Xmx2048M
先附上测试代码:
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class MemoryRecycleTest {
@Test
public void testMemoryRecycle() throws InterruptedException {
List list = new ArrayList();
//指定要生产的对象大小为512m
int count = 512;
//新建一条线程,负责生产对象
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
System.out.println(String.format("第%s次生产%s大小的对象", i, count));
addObject(list, count);
//休眠40秒
Thread.sleep(i * 10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//新建一条线程,负责清理list,回收jvm内存
new Thread(() -> {
for (;;) {
//当list内存到达512m,就通知gc回收堆
if (list.size() >= count) {
System.out.println("清理list.... 回收jvm内存....");
list.clear();
//通知gc回收
System.gc();
//打印堆内存信息
printJvmMemoryInfo();
}
}
}).start();
//阻止程序退出
Thread.currentThread().join();
}
public void addObject(List list, int count) {
for (int i = 0; i < count; i++) {
OOMobject ooMobject = new OOMobject();
//向list添加一个1m的对象
list.add(ooMobject);
try {
//休眠100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class OOMobject{
//生成1m的对象
private byte[] bytes=new byte[1024*1024];
}
public static void printJvmMemoryInfo() {
// 虚拟机级内存情况查询
long vmFree = 0;
long vmUse = 0;
long vmTotal = 0;
long vmMax = 0;
int byteToMb = 1024 * 1024;
Runtime rt = Runtime.getRuntime();
vmTotal = rt.totalMemory() / byteToMb;
vmFree = rt.freeMemory() / byteToMb;
vmMax = rt.maxMemory() / byteToMb;
vmUse = vmTotal - vmFree;
System.out.println("");
System.out.println("JVM内存已用的空间为:" + vmUse + " MB");
System.out.println("JVM内存的空闲空间为:" + vmFree + " MB");
System.out.println("JVM总内存空间为:" + vmTotal + " MB");
System.out.println("JVM总内存最大堆空间为:" + vmMax + " MB");
System.out.println("");
}
}
首先使用CMS垃圾回收器:
- 将jvm运行参数设置为如下:
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC
- 运行程序后,使用JProfiler查看堆内存情况:
- 查看控制台打印的内容:
第1次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:6 MB
JVM内存的空闲空间为:936 MB
JVM总内存空间为:942 MB
JVM总内存最大堆空间为:1990 MB
第2次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:1025 MB
JVM总内存空间为:1029 MB
JVM总内存最大堆空间为:1990 MB
第3次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:680 MB
JVM总内存空间为:684 MB
JVM总内存最大堆空间为:1990 MB
第4次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
第5次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
第6次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
第7次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
第8次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
第9次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:119 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1990 MB
- 查看jmap heap 信息:
C:\Users>jmap -heap 4716
Attaching to process ID 4716, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2122317824 (2024.0MB)
NewSize = 44695552 (42.625MB)
MaxNewSize = 348913664 (332.75MB)
OldSize = 89522176 (85.375MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 280887296 (267.875MB)
used = 1629392 (1.5539093017578125MB)
free = 279257904 (266.3210906982422MB)
0.5800874668251284% used
Eden Space:
capacity = 249692160 (238.125MB)
used = 1629392 (1.5539093017578125MB)
free = 248062768 (236.5710906982422MB)
0.6525603366961942% used
From Space:
capacity = 31195136 (29.75MB)
used = 0 (0.0MB)
free = 31195136 (29.75MB)
0.0% used
To Space:
capacity = 31195136 (29.75MB)
used = 0 (0.0MB)
free = 31195136 (29.75MB)
0.0% used
concurrent mark-sweep generation:
capacity = 624041984 (595.1328125MB)
used = 4169296 (3.9761505126953125MB)
free = 619872688 (591.1566619873047MB)
0.6681114583470076% used
6718 interned Strings occupying 574968 bytes.
通过统计图和控制台日志,可以看到在运行43秒左右前,使用内存呈直线平滑上升,开辟的内存呈阶梯状上升。当使用内存到达525m时,程序发起了System.gc(),此时垃圾被回收了,因此使用内存回到了10m,可是jvm开辟出来的内存空间却没有归还给操作系统,导致程序一直霸占着960m左右的内存资源。第二次生产对象时,可以看到在运行53秒至1分44秒时,不再开辟新空间,而是重复利用已开辟的内存继续创建对象,当执行第二次System.gc()时,jvm又开辟了一小部分内存,这一次程序霸占了1050m内存资源。第三次生产对象时,可以看到在运行2分05秒至2分55秒时,不再开辟新空间,而是重复利用已开辟的内存继续创建对象,当执行到第三次System.gc()时,jvm归还了一部分内存给操作系统,此时依然霸占着700m内存。........循环执行10次......从总的情况,可以看出,随着System.gc()次数逐渐增加和时间间隔逐渐拉大,从继续开辟内存变成了慢慢归还内存给了操作系统,直到后面将物理内存全部归还给操作系统。
接下来使用G1垃圾回收器:
-Xms128M -Xmx2048M -XX:+UseG1GC
- 运行程序后,使用JProfiler查看堆内存情况:
- 查看控制台打印的内容:
第1次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:5 MB
JVM内存的空闲空间为:123 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第2次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第3次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第4次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第5次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第6次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第7次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第8次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
第9次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:4 MB
JVM内存的空闲空间为:124 MB
JVM总内存空间为:128 MB
JVM总内存最大堆空间为:2024 MB
- 查看jmap heap 信息:
C:\Users>jmap -heap 18112
Attaching to process ID 18112, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2122317824 (2024.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 1272971264 (1214.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 1048576 (1.0MB)
Heap Usage:
G1 Heap:
regions = 2024
capacity = 2122317824 (2024.0MB)
used = 8336616 (7.950416564941406MB)
free = 2113981208 (2016.0495834350586MB)
0.39280714253663074% used
G1 Young Generation:
Eden Space:
regions = 2
capacity = 83886080 (80.0MB)
used = 2097152 (2.0MB)
free = 81788928 (78.0MB)
2.5% used
Survivor Space:
regions = 0
capacity = 0 (0.0MB)
used = 0 (0.0MB)
free = 0 (0.0MB)
0.0% used
G1 Old Generation:
regions = 11
capacity = 50331648 (48.0MB)
used = 6239464 (5.950416564941406MB)
free = 44092184 (42.049583435058594MB)
12.396701176961264% used
6706 interned Strings occupying 573840 bytes.
通过统计图和控制台日志,可以看到在运行41秒左右前,使用内存呈直线平滑上升,开辟的内存也是呈直线平滑上升。当使用内存到达530m时,程序发起了System.gc(),垃圾被回收,因此使用内存回到了10m。此时会发现神奇的现象出来了,jvm之前开辟出来的剩余内存空间全部归还给了操作系统,内存回到了我们指定的初始jvm堆大小128m。通过多次执行生产对象对比发现,jvm都是在每一次调用System.gc()后全部归还物理内存,不做任何保留。达到了我期望的效果!
总结:
CMS垃圾回收器,在内存开辟后,会随着System.gc()执行次数逐渐增多和回收频率逐渐拉长,从继续开辟内存到慢慢归还物理内存给操作系统,直到出现一次全部归还,就会在每次调用System.gc()都归还所有剩余的物理内存给操作系统;G1恰恰相反,G1是在JVM每次回收垃圾后,主动归还物理内存给操作系统,不做任何保留,大大降低了内存占用。
另外,查看java堆栈实时情况,推荐使用JProfiler和VisualVM。如果是本地推荐JProfiler,因为功能强大,不过远程配置麻烦;如果是连远程java进程,推荐VisualVM,功能够用,连接远程只需配置一些jvm参数。
其它说明
JDK 12将有G1收集器,将内存返回到操作系统(不调用System.gc)“应用程序空闲时”
jdk9 增加了这个jvm参数:
-XX:+ShrinkHeapInSteps
使Java堆渐进地缩小到目标大小,该选项默认开启,经过多次GC后堆缩小到目标大小;如果关闭该选项,那么GC后Java堆将立即缩小到目标大小。如果希望最小化Java堆大小,可以关闭改选项,并配合以下选项:
-XX:MaxHeapFreeRatio=10 -XX:MinHeapFreeRatio=5
这样将保持Java堆空间较小,并减少程序的动态占用空间,这对嵌入式应用非常有用,但对于一般应用,可能降低性能。
参考资料:
http://www.imooc.com/wenda/detail/574044
https://developer.ibm.com/cn/blog/2017/still-paying-unused-memory-java-app-idle/
https://gameinstitute.qq.com/community/detail/118528
https://www.zhihu.com/question/30813753
https://www.zhihu.com/question/29161424
JVM调优之探索CMS和G1的物理内存归还机制的更多相关文章
- JVM调优(二)经验参数设置
调优设置具体解析 堆大小设置 JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制:系统的可用虚拟内存限制:系统的可用物理内存限制.32位系统下,一般限制在1.5 ...
- 深入理解JAVA虚拟机(内存模型+GC算法+JVM调优)
目录 1.Java虚拟机内存模型 1.1 程序计数器 1.2 Java虚拟机栈 局部变量 1.3 本地方法栈 1.4 Java堆 1.5 方法区(永久区.元空间) 附图 2.JVM内存分配参数 2.1 ...
- jvm调优的分类
本文部分内容出自https://blog.csdn.net/yang_net/article/details/5830820 调优步骤: 衡量系统现状. 设定调优目标. 寻找性能瓶颈. 性能调优. 衡 ...
- JVM调优之经验
在生产系统中,高吞吐和低延迟一直都是JVM调优的最终目标,但这两者恰恰又是相悖的,鱼和熊掌不可兼得,所以在调优之前要清楚舍谁而取谁.一般计算任务和组件服务会偏向高吞吐,而web展示则偏向低延迟才会带来 ...
- 网络摘抄-深入浅出JVM调优
基本概念: JVM把内存区分为堆区(heap).栈区(stack)和方法区(method).由于本文主要讲解JVM调优,因此我们可以简单的理解为,JVM中的堆区中存放的是实际的对象,是需要被GC的.其 ...
- JVM调优和深入了解性能优化
JVM调优的本质: 并不是显著的提高系统性能,不是说你调了,性能就能提升几倍或者上十倍,JVM调优,主要调的是稳定.如果你的系统出现了频繁的垃圾回收,这个时候系统是不稳定的,所以需要我们来进行JVM调 ...
- JVM调优参数、方法、工具以及案例总结
这种文章挺难写的,一是JVM参数巨多,二是内容枯燥乏味,但是想理解JVM调优又是没法避开的环节,本文主要用来总结梳理便于以后翻阅,主要围绕四个大的方面展开,分别是JVM调优参数.JVM调优方法(流程) ...
- 【JVM进阶之路】十:JVM调优总结
1.调优原则 JVM调优听起来很高大上,但是要认识到,JVM调优应该是Java性能优化的最后一颗子弹. 比较认可廖雪峰老师的观点,要认识到JVM调优不是常规手段,性能问题一般第一选择是优化程序,最后的 ...
- JVM调优基础到进阶
GC和GC Tuning GC的基础知识 1.什么是垃圾 C语言申请内存:malloc free C++: new delete c/C++ 手动回收内存 Java: new ? 自动内存回收,编程上 ...
随机推荐
- 获取其他进程中StatusBar的文本
(*// 标题:获取其他进程中StatusBar的文本 说明:Window2000+Delphi6调试通过 设计:Zswang 支持:wjhu111@21cn.com 日期:2005-02-22 // ...
- OpenSSL包括了8个功能
什么是OpenSSL 众多的密码算法.公钥基础设施标准以及SSL协议,或许这些有趣的功能会让你产生实现所有这些 算法和标准的想法.果真如此,在对你表示敬佩的同时,还是忍不住提醒你:这是一个令人望而生畏 ...
- Windows 64 位下安装 psyco 1.6
用 eclipse 运行 python 的时候,第一行总是有红色提示:没有安装 psyco,程序可以正常运行但是会有一点慢.于是就干脆装上吧,红色的提示还是越少越舒服. 百度了一下,在这里,http: ...
- Codility--- NumberOfDiscIntersections
Task description We draw N discs on a plane. The discs are numbered from 0 to N − 1. A zero-indexed ...
- python 方法无法在线程中使用(附python获取网络流量)
在python中,定义一个方法,直接调用可以,但是创建一个线程来调用就可能导致失败.这种现象多出现在使用com对象进行系统操作时,而且是以线程的形式调用. 异常提示如下:syntax error.WM ...
- python三大主流web框架之Django安装、项目搭建
这一篇我们将迎来python强大的web框架Django,相信大家都已经不陌生,本篇将介绍Django的安装及基础项目搭建,大神略过~ Django是需要我们手动pip安装的,首先我们来安装Djang ...
- Hive 学习之路(八)—— Hive 数据查询详解
一.数据准备 为了演示查询操作,这里需要预先创建三张表,并加载测试数据. 数据文件emp.txt和dept.txt可以从本仓库的resources目录下载. 1.1 员工表 -- 建表语句 CREAT ...
- Java学习笔记——三层架构
Layer: UI层: user interface 用户接口层 Biz层: service business login layer 业务逻辑层 DAO层: Date Access Obje ...
- java与WebService对接案例--生成代码方法
前端时间出差做项目,因为我们给对三方工厂做Mes项目,其中有一个报工环节,需要我们的Mes中将产品提交到他们的U9(Erp)上,但是由于u9是用友的产品,用c#写的,而我是用java写的,那么WebS ...
- C++ 洛谷 P2657 [SCOI2009]windy数 题解
P2657 [SCOI2009]windy数 同步数位DP 这题还是很简单的啦(差点没做出来 个位打表大佬请离开(包括记搜),我这里讲的是DP!!! 首先Cal(b+1)-Cal(a),大家都懂吧(算 ...