假设一个项目,最大堆内存为512MB,以及开启了JMX管理,没有其他改动
那么,其在启动的测试结果为
由上面可以看出来,主要的占用时间是Compile time / Class Load Time / GC Time,而非用户线程,故具有很大的调优价值
对于其,我们采用的调用方式如下
1.首先尝试升级虚拟机版本,试图获取一些 免费的 性能提升
根据调查可以看出来,在每一次的大版本的升级之后,都会带来一些JVM关于垃圾回收性能上的优化,所以可以尝试从JAVA的版本升级上获取到一些性能上优化
但是别忘了,在一个项目中,尝试升级JDK,会带来着某些不可见的Bug
2.修改Compile time,进行优化
主要是对JIT,也就是热点代码编译的耗时优化,Java语言为了实现跨平台的特性,是使用了编译后的代码进行的存储,让虚拟机通过解释的方式进行执行,在之后的版本过程中,如果一段Java方法被调用次数达到了一定的程度,那么就会被判定为有热代码交给JIT编译成为本地代码,提高运行速度,甚至可能比C编译期编译出来的更加优秀
所以Java在运行之后会越来越快
虽然可以通过 -X int来禁止编译期运行,保证启动的速度,但是使用后会导致运行更慢
常见的编译期分为了C1 和 C2,
常见的是C1的轻量级编译器,但是还有一个C2的重量级编译器,虽然C2会导致额外的编译时间,但是一个项目长期的运行下去,其编译的会越来越快
3.对于GC Time的优化
关于GC时间的调整,对于类加载和编译时间被放大的情况下,不断的出现新的类加载和卸载,在运行一段之后,则时便随着JIT而变得更加快
但是GC则时持续不断的运行的,所以其极为重要
在项目启动过程中,短短15秒,发生了19次Full GChe 378 Minor GC,造成了4秒停顿
首先是新生代的Minor GC,虽然新生代的总时间不到一秒,但是发生了378次,这样过于频繁的GC会导致没必要的安全点检测和挂起
这样这么频繁,还是因为虚拟机分配给新生代的空间小而导致的,Eden区加上Survivor区不到35MB,故采用-Xmn来调整新生代的大小
上面可以看出每次的Full GC都伴随着老年代的扩容,从最开始的1536KB到46828KB,
而且在某些情况下,只是单纯的不回收,将空间进行了扩容
所以为了避免带来扩容的性能浪费,可以考虑一种调优方式,就是讲老年代和永久代的容量进行固定下来,避免多次扩容
经过了将两者空间的大小配置,最后的项目启动时间,只发生了8次MirrorGC和4次FullGC,总耗时降低了3/4
4.现在还差最后一步,就是关于收集器的选择
对于原本默认的Serial全家桶,改为了更为符合这个场景的CMS收集器,而且配合使用多线程的ParNew收集器,在原本的单线程收集器的基础上对比,老年代停顿降到了每次的725毫秒到35毫秒