实战:IDEA运行速度调优
序言
可能大家觉得系统调优一般都是针对服务端应用而言的,普通Java开发人员很少有机会实践。今天就通用一个Java开发人员日常工作中经常使用的开发工具开做一次调优实战。
我在日常工作中的主要IDE工具是IntelliJ IDEA,由于安装的插件较多,项目代码也比很多,所以运行速度不是特别令人满意,所以决定对其进行调优。
IDEA的运行平台是64位Windows10系统,虚拟机为HotSpot 1.8 b64。硬件为Intel i7-10510U,8GB物理内存。
初始JVM参数配置如下:
-Xmx512m
-XX:ReservedCodeCacheSize=240m
-XX:SoftRefLRUPolicyMSPerMB=80
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
为了方便与调优后的结果做对比,在开始前先做一组初始数据测试。
由于无法得知IDEA启动的准确耗时,我们通过VisualGC收集到的信息,总结初始配置下的测试结果:
最后一次启动的数据样本中,垃圾收集总耗时3.102秒,其中:
- FULL GC被触发了6次,共耗时1.109秒。
- Minor GC被触发了89次,共耗时1.933秒。
- 加载类41148个,耗时28.908秒。
- 虚拟机的512MB堆内存被分配为50MB的新生代(40MB的Eden区和两个5MB的Survivor区)和462MB的老年代。

图:VisualVM监控
1、编译时间和类加载优化
通过测试数据可以看到,加载类和编译的时间非常耗时,而在其中字节码校验耗时占较大的比例。IDEA作为一款广为使用的成熟产品,它的编译代码我们可以认为是安全可靠的,不需要在加载过程中再进行字节码验证,因此可以通过-Xverify:none参数禁止掉字节码校验过程。

图:类的生命周期
在加入这个参数后,类的加载速度得到了一定的提升,加载时间缩减到19秒左右。

2、调整内存设置控制垃圾收集频率
下面我们要对“GC时间”进行调整优化,“GC时间”是最为重要的一块,这不单单是因为它消耗的时间较长,而是因为垃圾回收是一个稳定而持续的过程。在当前的测试用例中,加载类和即使编译的时间所占比例看上去比较高,但是在绝大多数的应用中,不可能出现持续的类加载和卸载过程。程序在运行了一段时间后,随着热点方法被不断的编译,新的热点方法数量也会下降,这会让类加载和即时编译所占的时间比例随着运行时间的增加而逐渐下降。但是垃圾回收却是随着程序运行而持续运作的,所以它才是对性能影响最重要的部分。
从测试的样本中来看,在IDEA启动过程中,共发生了6次FULL GC和89次Minor GC,一共95次GC造成了约3秒的停顿。
首先来分析新生代的Minor GC,尽管垃圾收集时间只有不到2秒,但是发生了89次之多。由于每次垃圾回收都需要用户线程跑到最近的安全点然后挂起来等待垃圾回收,频繁的垃圾收集必然会导致很多没有必要的线程挂起和恢复动作。
新生代垃圾收集频繁很明显是由于虚拟机分配的新生代空间太小导致的,Eden区加上一个Survivor区才45MB。所以我们通过-Xmn参数指定新生代大小为128MB。
再来看那6次FULL GC,虽然触发次数较少,但是平均每次的耗时要比较于Minor GC要高的多,所以降低垃圾收集的停顿时间的主要目标就是要降低FULL GC时间。我们从GC日志中分析得到FULL GC的原因,从GC日志中截取FULL GC部分日志。
[Full GC (Metadata GC Threshold) 2020-05-23T14:55:15.403+0800: 4.363: [Tenured: 56560K->54011K(77824K), 0.0691307 secs] 71164K->54011K(123904K), [Metaspace: 34482K->34482K(1081344K)], 0.0692415 secs] [Times: user=0.06 sys=0.00, real=0.07 secs] [Full GC (Metadata GC Threshold) 2020-05-23T14:55:15.403+0800: 4.363: [Tenured: 56560K->54011K(77824K), 0.0691307 secs] 71164K->54011K(123904K), [Metaspace: 34482K->34482K(1081344K)], 0.0692415 secs] [Times: user=0.06 sys=0.00, real=0.07 secs] [Full GC (Metadata GC Threshold) 2020-05-23T14:55:17.319+0800: 6.279: [Tenured: 67919K->65602K(90020K), 0.1222525 secs] 78376K->65602K(136100K), [Metaspace: 55927K->55927K(1099776K)], 0.1224057 secs] [Times: user=0.13 sys=0.00, real=0.12 secs] [Full GC (Metadata GC Threshold) 2020-05-23T14:55:19.520+0800: 8.480: [Tenured: 81857K->76282K(109340K), 0.1617177 secs] 105366K->76282K(155420K), [Metaspace: 91958K->91958K(1132544K)], 0.1618629 secs] [Times: user=0.19 sys=0.00, real=0.16 secs] [GC (Allocation Failure) 2020-05-23T14:55:29.399+0800: 18.360: [DefNew: 46080K->3039K(46080K), 0.0338515 secs]2020-05-23T14:55:29.433+0800: 18.394: [Tenured: 129641K->112262K(129828K), 0.3193513 secs] 170759K->112262K(175908K), [Metaspace: 147845K->147845K(1183744K)], 0.3535963 secs] [Times: user=0.36 sys=0.00, real=0.35 secs] [GC (Allocation Failure) 2020-05-23T14:55:40.032+0800: 28.992: [DefNew: 46080K->5120K(46080K), 0.0528825 secs]2020-05-23T14:55:40.085+0800: 29.044: [Tenured: 198033K->176461K(198052K), 0.4157306 secs] 231627K->176461K(244132K), [Metaspace: 194691K->194691K(1224704K)], 0.4690424 secs] [Times: user=0.48 sys=0.00, real=0.47 secs]
日志中加粗的部分代表着老年代的容量,几乎每一次FULL GC的原因都是老年代空间耗尽,每一次FULL GC都伴随着老年代空间的扩容:77824K → 90020K → 109340K → 129828K → 198052K。
日志中还显示有些时候内存回收效果不理想,空间扩容成了获取可用内存的最主要手段,比如这一句:[Tenured: 81857K->76282K(109340K), 0.1617177 secs]
代表在老年代当前容量为109340KB,内存使用到81857K时发生了FULL GC,花费了0.3193513秒时间把内存使用降低到76282K,回收了5575KB的内存空间。但是这次垃圾回收未达到效果,触发了空间扩容。扩容相比起回收过程可以看做基本不需要花费时间,所以这0.3193513秒时间几乎是浪费了。
由上述分析可以得到一个结论,FULL GC大多数是由于老年代容量扩展而导致的。那怎样避免扩容时的性能浪费呢,可以把-Xms参数设置为-Xmx参数值一样。将堆栈的空间固定下来,避免了运行时的自动扩展。
由于元空间(Metaspace)的扩展也占据了一部分的垃圾收集时间,我们可以通过设置一个元空间初始值来避免掉一部分扩展。(由于元空间的默认最大值是不受限制的,即只受限于本地内存大小,故只调整初始空间大小)
注:截取的GC日志,显示元空间的扩展也消耗了部分GC时间。
[Metaspace: 55927K->55927K(1099776K)], 0.1224057 secs]
[Metaspace: 91958K->91958K(1132544K)], 0.1618629 secs]
根据以上的分析,优化方案如下:
- 新生代空间提升到128MB(-Xmn128m)
- Java堆容量固定为512MB(-Xms512m、-Xmx512m)
- 元空间容量初始值设置为(-XX:MetaspaceSize=250m)
调整后的JVM配置
-Xms512m
-Xmx512m
-Xmn128m
-XX:MetaspaceSize=250m
-Xverify:none
在这个配置下再次测试,垃圾收集的次数已经大幅降低,只发生了34次Minor GC和1次FULL GC,总耗时1.823秒。

图:调整后的JVM运行数据
从结果看,优化效果很明显,但是有一点疑问,从Old Gen的曲线上来看,老年代的空间直接固定在384MB,而内存使用量还不足以触发FULL GC。那一次FULL GC是怎么来的呢?查看GC日志来查明原因。
2020-05-23T16:02:56.546+0800: 21.430: [Full GC (System.gc()) 2020-05-23T16:02:56.546+0800: 21.430: [Tenured: 160328K->152291K(393216K), 0.4226741 secs] 221862K->152291K(511232K), [Metaspace: 143107K->143107K(1181696K)], 0.4228607 secs] [Times: user=0.42 sys=0.00, real=0.42 secs]
原来是代码中显示的调用了System.gc()触发了垃圾收集,在内存设置调整后,这种显式的垃圾收集不符合我们的期望。在JVM参数中加入-XX:DisableExplicitGC屏蔽掉System.gc()。
3、选择收集器降低延迟
通过查看启动期间的CPU使用情况,我们可以看到CPU的平均使用率并不高,垃圾收集的处理器使用率就更低了,几乎和横坐标紧贴在一起。这说明处理器资源还很富足。

Java虚拟机提供了多种垃圾收集器的组合,很容易想到CMS是最符合当前场景的选择。
在JVM配置中加入这两个参数:
-XX:+UseParNewGC、-XX:+UseConcMarkSweepGC;
要求虚拟机在新生代和老年代分别使用ParNew和CMS收集器来进行垃圾回收。

图:指定ParNew和CMS收集器后的GC数据
再次测试后,新生代停顿553毫秒,老年代停顿96毫秒,总耗时降低为649毫秒。相比较于调整垃圾收集器前快了将近三倍之多。
当然,由于CMS的停顿时间只是整个收集过程中的一小部分,大部分收集行为都是和用户线程并发执行的。所以这里并不是真的将收集时间降低到649毫秒了。
到此为止,对于IDEA的JVM调优就结束了,我们终于可以愉快的使用IDEA进行工作了!最终的配置清单如下所示。
# custom IntelliJ IDEA VM options
-Xms512m
-Xmx512m
-Xmn128m
-XX:MetaspaceSize=250m
-Xverify:none
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=85
-XX:+DisableExplicitGC-XX:ReservedCodeCacheSize=240m
-XX:SoftRefLRUPolicyMSPerMB=80
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
链接: 文章首发地址
实战:IDEA运行速度调优的更多相关文章
- 素小暖讲JVM:Eclipse运行速度调优
本系列是用来记录<深入理解Java虚拟机>这本书的读书笔记.方便自己查看,也方便大家查阅. 欲速则不达,欲达则欲速! 这两天看了JVM的内存优化,决定尝试一下,对Eclipse进行内存调优 ...
- Netty实战之性能调优与设计模式
设计模式在Netty 中的应用(回顾): 单例模式要点回顾: 一个类在任何情况下只有一个对象,并提供一个全局访问点. 可延迟创建. 避免线程安全问题. 在我们利用netty自带的容器来管理客户端链接的 ...
- 《深入理解Java虚拟机》-----第5章 jvm调优案例分析与实战
案例分析 高性能硬件上的程序部署策略 例 如 ,一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU.16GB物理内存,操作系统为64位CentOS 5.4 , Resin ...
- 【JVM.4】调优案例分析与实战
之前已经介绍过处理Java虚拟机内存问题的知识与工具,在处理实际项目的问题时,除了知识与工具外,经验同样是一个很重要的因素.本章会介绍一些具有代表性的案例. 本章的内容推荐还是原文全篇看完的好,实在不 ...
- Spark Shuffle调优原理和最佳实践
对性能消耗的原理详解 在分布式系统中,数据分布在不同的节点上,每一个节点计算一部份数据,如果不对各个节点上独立的部份进行汇聚的话,我们计算不到最终的结果.我们需要利用分布式来发挥Spark本身并行计算 ...
- Linux操作系统性能调优的方法
http://www.cnblogs.com/L-H-R-X-hehe/p/3963442.html Linux是一套免费使用和自由传播的类Unix操作系统,Linux不同的发行版本和不同的内核对各项 ...
- Java性能调优攻略全分享,5步搞定!(附超全技能图谱)
对于很多研发人员来说,Java 性能调优都是很头疼的问题,为什么这么说?如今,一个简单的系统就囊括了应用程序.数据库.容器.操作系统.网络等技术,线上一旦出现性能问题,就可能要你协调多方面组件去进行优 ...
- JVM调优实战
JVM调优实战 文档修订记录 版本 日期 撰写人 审核人 批准人 变更摘要 & 修订位置 ...
- 《深入理解Java虚拟机》调优案例分析与实战
上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...
随机推荐
- 浅谈SIEM
一.概念 SIEM ( Security Information Event Management,安全信息与事件管理) Gartner的定义:安全信息和事件管理(SIEM)技术通过对来自各种事件和上 ...
- 【Java8新特性】关于Java8中的日期时间API,你需要掌握这些!!
写在前面 Java8之前的日期和时间API,存在一些问题,比如:线程安全的问题,跨年的问题等等.这些问题都在Hava8中的日期和时间API中得到了解决,而且Java8中的日期和时间API更加强大.立志 ...
- unittest单元测试框架入门及应用
一.简介 unittest是Python单元测试框架.unittest它支持自动化测试,在测试中使用setup(初始化)和shutdown(关闭销毁)操作,组织测试 用例为套件(批量运行),以及把测试 ...
- set基本运用
/* set集合基本用法 */ #include<iostream> #include<set> using namespace std; //set<T>a; v ...
- vim编辑器添加插件NERDTree
0x01 首先在 http://www.vim.org/scripts/script.php?script_id=1658 下载插件 (可能要爬梯,也可以在https://github.com/scr ...
- Cypress系列(16)- 查找页面元素的基本方法
如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html 前端页面代码 后面写的 Cypress ...
- STM32的8*8点阵屏开发(小项目)
基础认识 实现效果 项目实现STM32点阵屏的操作,自动更改显示内容和串口控制显示内容 STM32上电后: 1) 程序将进行行和列的刷新 2) 自动递增显示0-9变化 3) 进行矩形由内向 ...
- 解决intellij idea卡顿的方法
使用idea越用越卡,即使是16G内存也是卡,多开几个微服务卡死了!! 解决方案 参考网路资源整理如下几条 1. 卸载不需要用的插件 我是Java开发,对于一些默认安装的什么安卓的google的app ...
- 停电后,在UPS电源下服务器自动关机脚本
一年总有那么几次莫明停电,公司的服务器经不起这样的折腾 写了一个断电后UPS备用电源自动关机的脚本 原理就是检测路由器网关是否能ping通,长时间持续ping不通视为停电了 路由器不要接到ups上 用 ...
- 【转载】有人出天价买他的一个文案标题,今天10min教会你……
目录 1. 套路 1:新闻社论 2. 套路 2:好友对话 3. 套路 3:实用锦囊 4. 套路 4:惊喜优惠 5. 套路 5:意外故事 本文由 简悦 SimpRead 转码, 原文地址 https:/ ...