在上篇中我们粗略介绍了一下系统优化的几种类型、JVM的内存分区、JVM的垃圾回收算法、垃圾回收的类型。接下来我们根据上述的一些知识,分析下如何恰当的选择我们的垃圾回收器,如何进行JVM的调优。
1.垃圾回收器选择
1.1垃圾回收器对比

可以看到,基于复制算法的垃圾回收器有三种,它们都是用于新生代的。Serial适用于单CPU的场景,当然单CPU也无法使用其他算法。它的单线程性能比其他收集器要高,但是还是打不过一群人,双拳难敌四手。ParNew适用于多CPU场景,这个时候再去用Serial就不太划算了。它是并发并行模式,性能上肯定有提升,但是GC的时候存在STW。Prallel Scavenge它最核心的就是优化了并发并行,它的回收流程进行细致分解,在必须STW的情况下才STW,并且它还能控制STW时间,在JDK6开始可以用Parallel Scavenge了。
用于老年代的收集器有三种,它们基于不同的垃圾回收算法。Serial Old不用说,还是单CPU场景。Prallel Old,Prallel Scavenge的老年代版本,也不再多说。CMS,在JDK7开始这种被长使用,直到JDK9开始才慢慢被G1取代,它的有点很明显低停顿高性能的优点。
1.2垃圾回收器选择
在上篇中放过一张图,这个图连线之间的就是可以配合使用的,不能连线的就不能配合使用。它的配合使用一般有四种:
serial & serial old
client模式下默认的垃圾收集器组合,可通过-XX:+UseSerialGC强制开启。
非常适合运行于客户端PC的小型应用程序,或者桌面应用程序(比如swing编写的用户界面程序),以及我们平时的开发、调试、测试等。
开发、调试、测试共同的特点:
● 由于都是在PC上运行,因此配置一般不会太高,或者说处理器个数不会太多。
● 上面几种情况的应用程序都不会运行太久。
● 规模不会太大,也就是说,堆相对较小,搜集起来也比较快,停顿时间会比较短。
Parallel Scavenge & Parallel Old
这个组合是server模式下的默认组合(JDK6或JDK6之后),使用-XX:+UseParallelGC参数强制开启。
适用于一些需要长期运行且对吞吐量有一定要求的后台程序。
运行于后台的程序都有以下特点:
● 系统配置较高,通常情况下至少四核(以目前的硬件水平为准)。
● 对吞吐量要求较高,或需要达到一定的量。
● 应用程序运行时间较长。
● 应用程序规模较大,一般是中到大型的堆。
ParNew & CMS(Serial Old作为替补)
它则是对响应时间(response time)要求较高的应用程序的首选,使用参数-XX:+UseConcMarkSweepGC开启。
新生代采用并行搜集器适用于一些需要长期运行且对相应时间有一定要求的后台程 序。
采用ParNew & CMS组合的后台应用程序,一般都对相应时间有一定要求,最典型的就是我们的WEB应用程序,其特点:
● CPU核数至少在6-8之间
● JDK6-JDK8
G1
可以看作是ParNew & CMS 的升级版,使用参数-XX:+UseG1GC开启。
G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。其特点:
● CPU要求高、有JDK版本要求
● 空间整合,没有碎片
● 可预测的停顿时间
● 每个Region不会固定分配给老年代还是年轻代,按需分配
2.JVM参数
在上面对垃圾回收器的选择中,我们可以看到一些开启这些垃圾回收器的参数,但是,在实际中这些参数只是我们JVM参数的九牛一毛。为了方便理解这些参数,我将这些参数分为三类:
标准选项:
java -
java -cp
java -client
java -classpath
非标准选项:
java -X
java -Xmx256m
java -Xloggc:1.txt
高级选项:
java -XX
java -XX:+UnlockExperimentalVMOptions
java -XX:+UnlockDiagnosticVMOptions
java -XX:+PrintFlagsInitial
解释一下,标准选项一般跟我们JVM基本属性有关,例如java -version。它们有标识启动模式的,有表示类加载路径的。非标准选项内,我简单的认为是JVM分区的一些参数,但是里面又含带了一些收集器日志打印之类的参数。高级选项,这个一般就是指我们很少去了解并用到的参数,比如垃圾收集器的选择、垃圾收集器的控制、远程模式开启、断点模式开启、代理模式开启等。初学者知道标准,中级知道非标,高级知道高级,大概就这么个意思。
由于参数太多了,我们选择一个看看,非标参数。

我们可以看到,我们能使用非标参数设置堆内存的最大及初始值,设置新生代、年老代的大小,可以调整它们之间的比例。
3.一个实例
读了上面两个小结,我们想一个实际开发中的例子来佐证我们的描述。问:在一个spingboot工程,使用1.8jdk直接进行启动(未进行任何设置)之后,它的新生代、年老代是多少?它的垃圾回收机制是什么样的。
不卖关子啊,使用jps –l 获取进程号,jmap -heap 进程号来获取堆内存信息,jinfo -flags 进程号来获取jvm参数信。我们得到了如下两张图:

这里我们可以看到最大堆内存是2G左右,符合默认物理内存1/4的逻辑。最大新生代664.5MB,符合新生代:年老代=1:3。同时,我们也可以看到伊甸园区、幸存者区、年老代的使用情况。这么我们可以知道JDK1.8的默认占用内存大小符合我们上面讲的参数。

这个是第二张图,我们可以看到JDK1.8模式是使用-XX:+UseParallelGC开启了Parallel Scavenge & Parallel Old收集器模式,也是符合之前我们对JVM垃圾收集器的解读。
垃圾收集器的知识远不止这些,如何学好JVM调优,还是要深入了解不同JDK版本的不同JVM参数的使用,任重而道远。




