0%

JVM调优实战 - 优化2015年8G内存 Macbook Pro 上 IDEA 的打开速度

调优前的程序运行状态

机器:

MacBook Pro (Retina, 13-inch, Early 2015)
cpu 2.7 GHz Intel Core i5
mem 8 GB 1867 MHz DDR3

IDEA 信息

IntelliJ IDEA 2019.1.4 (Ultimate Edition)
Build #IU-191.8026.42, built on July 30, 2019
JRE: 1.8.0_212-release-1586-b4 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.14

原始配置信息

-Xms128m
-Xmx750m

# 保留代码所用的Cache大小
#see https://juejin.im/post/5aebf997f265da0ba76f99db
-XX:ReservedCodeCacheSize=240m

#Enables the use of compressed pointers (object references represented as 32 bit offsets instead of 64-bit pointers) for optimized 64-bit performance with Java heap sizes less than 32gb.
-XX:+UseCompressedOops

-Dfile.encoding=UTF-8

#-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseConcMarkSweepGC

#-XX:SoftRefLRUPolicyMSPerMB=N 这个参数比较有用的,官方解释是:Soft reference在虚拟机中比在客户集中存活的更长一些。其清除频率可以用命令行参数 -XX:SoftRefLRUPolicyMSPerMB=<N>来控制,这可以指定每兆堆空闲空间的 soft reference 保持存活(一旦它不强可达了)的毫秒数,这意味着每兆堆中的空闲空间中的 soft reference 会(在最后一个强引用被回收之后)存活1秒钟。注意,这是一个近似的值,因为 soft reference 只会在垃圾回收时才会被清除,而垃圾回收并不总在发生。系统默认为一秒,我觉得没必要等1秒,客户集中不用就立刻清除,改为 -XX:SoftRefLRUPolicyMSPerMB=0;
-XX:SoftRefLRUPolicyMSPerMB=50

#enable assert
#-ea[:<package name>"..." | :<class name> ]
#上述参数就用来设置jvm是否启动断言机制(从JDK 1.4开始支持),缺省时jvm关闭断言机制。
#用-ea 可打开断言机制,不加<packagename>和classname时运行所有包和类中的断言,如果希望只运行某些包或类中的断言,可将包名或类名加到-ea之后。例如要启动包com.wombat.fruitbat中的断言,可用命令java -ea:com.wombat.fruitbat...<Main Class>。
-ea

#使用标准IO缓存
-Dsun.io.useCanonCaches=false

#优先使用IPv4栈
-Djava.net.preferIPv4Stack=true

#堆内存溢出输出
-XX:+HeapDumpOnOutOfMemoryError

#HotSpot Server Compiler(C2)会用fast throw来优化这个抛出异常的地方,直接抛出一个事先分配好的、类型匹配的对象,这个对象的message和stack trace都被清空。 配置该选项,会禁止该优化, 从而打印所有异常堆栈
-XX:-OmitStackTraceInFastThrow

#禁止字节码校验
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

观察参数方式

jps
jstat -gc pid 3s
jstat -gcutil pid 3s
jstat -gccause pid 3s
jinfo -flag MetaspaceSize pid

img

未修改配置

-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

Attachment.png

看到老年代容量进行了扩容(由于ms和mx不匹配)

第一步:调整 ms mx mn

-Xms750m
-Xmx750m
-Xmn280m

-Xms750m
-Xmx750m
#设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xmn280m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

Attachment.png

看OC这一列已经没有发生扩容行为了, 但仍然发生了FGC

新建/加载类导致 Metaspace 容量不够,触发 GC,GC 完成后重新计算 Metaspace 新容量,决定是否对 Metaspace 扩容或缩容

see: 阿菜:由「Metaspace容量不足触发CMS GC」从而引发的思考

且看到metaspace发生扩容容量不足导致频繁的fgc, 因此考虑增大这部分大小

如果没有配置-XX:MetaspaceSize,那么触发FGC的阈值是21807104 b(约21299.2kb, 20.8mb),可以通过jinfo -flag MetaspaceSize pid得到这个值

see: 阿飞:JVM参数MetaspaceSize的误解

第二步:增加metaspace大小

-Xms750m
-Xmx750m
#设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xmn280m

#指Metaspace扩容时触发FullGC的初始化阈值,也是最小的阈值
#Metaspace由于使用不断扩容到-XX:MetaspaceSize参数指定的量,就会发生FGC;且之后每次Metaspace扩容都可能会发生FGC(至于什么时候会,比较复杂,跟几个参数有关)
-XX:MetaspaceSize=256m
#如果MaxMetaspaceSize设置太小,可能会导致频繁FullGC,甚至OOM
#MetaspaceSize和MaxMetaspaceSize设置一样大;
#具体设置多大,建议稳定运行一段时间后通过jstat -gc pid确认且这个值大一些,对于大部分项目256m即可
-XX:MaxMetaspaceSize=512m

-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

Attachment.png

此时可以发现尽管MC一直在变大, 但是FGC没有增加. 因为没有达到metaspace Full GC 的要求(-XX:MetaspaceSize=256M)

第三步:显示指定垃圾收集器

其实这一步是不必要的,因为用jconsole查看垃圾回收器版本,在未指定的情况下已经是鲸鱼期望的 ParNew 和 CMS 了. 不过写清楚后, 下次就不需要再通过jconsole来查看了

image-20190821175654810

-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

-Xms750m
-Xmx750m
-Xmn280m

-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

第四步:进一步查看启动过程中的gc原因

image-20190822150749413

可以看到中间有一次显式调用了 System.gc(), 可以在jvm参数中禁止掉显式的gc调用

-XX:+DisableExplicitGC

-Xms750m
-Xmx750m
-Xmn280m

-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

-XX:+DisableExplicitGC

-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

image-20190822151559977

image-20190822151615817

此时gc总时间从一开始的2.5秒优化成了目前的1.433秒, 达到了一定的优化效果

参考:

https://www.jianshu.com/p/3eb976e03457

https://www.jianshu.com/p/ba2d613df94f

https://www.cnblogs.com/yjd_hycf_space/p/7755633.html

附录:

1.原始配置

-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar