软件开发架构师

记一次IDEA编译器调优

java 92 2019-02-25 15:17

前言:

我们知道,IDEA是用Java写的,那么他肯定也存在虚拟机的调优的问题,那么今天我们就对它进行开刀。

下面是默认参数 位置在:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2\bin\idea64.exe.vmoptions

 1 -Xms128m
 2 -Xmx756m
 3 -XX:ReservedCodeCacheSize=240m
 4 -XX:+UseConcMarkSweepGC
 5 -XX:SoftRefLRUPolicyMSPerMB=50
 6 -ea
 7 -Dsun.io.useCanonCaches=false
 8 -Djava.net.preferIPv4Stack=true
 9 -Djdk.http.auth.tunneling.disabledSchemes=""
10 -XX:+HeapDumpOnOutOfMemoryError
11 -XX:-OmitStackTraceInFastThrow

我们从这里可以看到,堆最小分配了128M,最大分配了756M。

新生代用的是ParNew收集器,基于标记复制算法

老年代用的是CMS,基于标记清除算法。

 

优化前:

我们这里用到了两个神器。jvisualvm.exe和jconsole.exe。

我们先启动一下IDEA

 

新生代进行了56次gc,老年代进行了5次Full gc。这肯定不能忍受的嘛。

 

优化一:

 1 -Xms1024m 2 -Xmx1024m 3 -Xmn350m  4 -XX:ReservedCodeCacheSize=240m
 5 -XX:+UseConcMarkSweepGC
 6 -XX:SoftRefLRUPolicyMSPerMB=50
 7 -ea
 8 -Dsun.io.useCanonCaches=false
 9 -Djava.net.preferIPv4Stack=true
10 -Djdk.http.auth.tunneling.disabledSchemes=""
11 -XX:+HeapDumpOnOutOfMemoryError
12 -XX:-OmitStackTraceInFastThrow
13 
14 -verbose:gc 15 -XX:+PrintGCDetails 16 -XX:+PrintGCDateStamps 17 -Xloggc:gcc.log

设置堆的最大最小都为1024M,不给动态扩容。并且设置新生代为350M,同时打出日志。

 

 

可以看到,只进行了7次新生代GC,3次老年代GC。

 

我很奇怪,为何老年代没有填满,并且还有好多空间,会发生三次FullGC呢?部分日志如下:

 1 2018-08-20T22:35:57.399+0800: 3.963: [CMS-concurrent-preclean-start]
 2 2018-08-20T22:35:57.399+0800: 3.965: [CMS-concurrent-preclean: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 3 2018-08-20T22:35:57.399+0800: 3.965: [CMS-concurrent-abortable-preclean-start]
 4 2018-08-20T22:35:59.457+0800: 6.018: [CMS-concurrent-abortable-preclean: 1.465/2.053 secs] [Times: user=6.16 sys=0.52, real=2.06 secs] 
 5 2018-08-20T22:35:59.458+0800: 6.019: [GC (CMS Final Remark) [YG occupancy: 167766 K (322560 K)]2018-08-20T22:35:59.458+0800: 6.019: [Rescan (parallel) , 0.0669837 secs]2018-08-20T22:35:59.525+0800: 6.086: [weak refs processing, 0.0000425 secs]2018-08-20T22:35:59.525+0800: 6.086: [class unloading, 0.0054995 secs]2018-08-20T22:35:59.530+0800: 6.091: [scrub symbol table, 0.0064731 secs]2018-08-20T22:35:59.537+0800: 6.098: [scrub string table, 0.0004637 secs][1 CMS-remark: 0K(690176K)] 167766K(1012736K), 0.0803359 secs] [Times: user=0.25 sys=0.00, real=0.08 secs] 
 6 2018-08-20T22:35:59.538+0800: 6.099: [CMS-concurrent-sweep-start]
 7 2018-08-20T22:35:59.538+0800: 6.099: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 8 2018-08-20T22:35:59.538+0800: 6.099: [CMS-concurrent-reset-start]
 9 2018-08-20T22:35:59.540+0800: 6.101: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
10 2018-08-20T22:36:03.352+0800: 9.915: [GC (Allocation Failure) 2018-08-20T22:36:03.352+0800: 9.915: [ParNew: 310560K->29155K(322560K), 0.0792661 secs] 310560K->47946K(1012736K), 0.0793506 secs] [Times: user=0.25 sys=0.02, real=0.08 secs] 
11 2018-08-20T22:36:07.438+0800: 14.013: [GC (CMS Initial Mark) [1 CMS-initial-mark: 18790K(690176K)] 255956K(1012736K), 0.0418642 secs] [Times: user=0.22 sys=0.00, real=0.06 secs] 
12 2018-08-20T22:36:07.515+0800: 14.076: [CMS-concurrent-mark-start]

 

因为老年代是采用CMS,我们来分析下,什么情况下CMS会导致FullGC。

1:担保失败,即在并发清除,会有新生代担保进来,如果不足,则提前出发Full GC。

可通过 -XX:CMSInitiatingOccupancyFraction 来调整比例。

2:因为CMS是标记清除算法,势必会产生大量的碎片,导致Full GC

可通过调整老年代的大小来进行避免。

 

但经过上面两个分析,并进行了调整,发觉仍然会发生FullGC,并且老年代的空间还剩余特别的多,根本不存在空间不足,或者找不到连续空间的问题,那么为何还会进行Full GC呢。

 

优化二:

 

 1 -Xms1024m
 2 -Xmx1024m
 3 -Xmn350m
 4 -XX:ReservedCodeCacheSize=240m
 5 -XX:+UseConcMarkSweepGC
 6 -XX:SoftRefLRUPolicyMSPerMB=50
 7 -ea
 8 -Dsun.io.useCanonCaches=false
 9 -Djava.net.preferIPv4Stack=true
10 -Djdk.http.auth.tunneling.disabledSchemes=""
11 -XX:+HeapDumpOnOutOfMemoryError
12 -XX:-OmitStackTraceInFastThrow
13 
14 -verbose:gc
15 -XX:+PrintGCDetails
16 -XX:+PrintGCDateStamps
17 -Xloggc:gcc.log
18 
19 -XX:MetaspaceSize=1024m

 

注意看最后一个参数-XX:MetaspaceSize=1024M

 

可以看到,此时一次FullGC都没有发生,新生代GC至发生了7次就已经完成了启动。

 

结论:

测试,我测试了好多遍,当然不会像博客这样简单的测试。差了好多资料,比了好多的数据。

优化前,大概启动进行50多次新生代GC,3-5次老年代GC。

优化后,进行7次左右新生代GC,0次老年代GC。

启动时间大概快了有10s。

 

分析一下为何,MetaSpaceGC 会引发一次CMS GC

 

 1 JDK8: Metaspace 
 2 In JDK 8, classes metadata is now stored in the native heap 
 3 and this space is called Metaspace. There are some new flags added for 
 4 Metaspace in JDK 8: 
 5 -XX:MetaspaceSize= where is the initial amount of space(the initial 
 6 high-water-mark) allocated for class metadata (in bytes) that may induce a 
 7 garbage collection to unload classes. The amount is approximate. After the 
 8 high-water-mark is first reached, the next high-water-mark is managed by 
 9 the garbage collector 
10 -XX:MaxMetaspaceSize= where is the maximum amount of space to be allocated for class 
11 metadata (in bytes). This flag can be used to limit the amount of space 
12 allocated for class metadata. This value is approximate. By default there 
13 is no limit set. 
14 -XX:MinMetaspaceFreeRatio= where is the minimum percentage of class metadata capacity 
15 free after a GC to avoid an increase in the amount of space 
16 (high-water-mark) allocated for class metadata that will induce a garbage collection. 
17 -XX:MaxMetaspaceFreeRatio= where is the maximum percentage of class metadata capacity 
18 free after a GC to avoid a reduction in the amount of space 
19 (high-water-mark) allocated for class metadata that will induce a garbage collection. 
20 By default class 
21 metadata allocation is only limited by the amount of available native memory. We 
22 can use the new option MaxMetaspaceSize to limit the amount of native memory 
23 used for the class metadata. It is analogous(类似) to MaxPermSize. A garbage collection is induced to collect the dead classloaders 
24 and classes when the class metadata usage reaches MetaspaceSize (12Mbytes on 
25 the 32bit client VM and 16Mbytes on the 32bit server VM with larger sizes on 
26 the 64bit VMs). Set MetaspaceSize to a higher value to delay the induced 
27 garbage collections. After an induced garbage collection, the class metadata usage 
28 needed to induce the next garbage collection may be increased.

 

JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。 

 

即如果MetaSpace不足,也会触发Full GC(在64为机器上默认20.8M)。那么调大即可,延迟Full GC的时间。

 

参考资料:

https://blog.csdn.net/liubenlong007/article/details/78143285

http://tech.dianwoda.com/2018/01/10/jdk8-de-fullgc-zhi-metaspace/

 

文章评论