1 缘起
日常补充JVM调优,调优实践前需要学习一些理论做支撑,
JVM调优三步:理论=>GC分析=>JVM调优,
我们会有一些玩笑话说,做了这么久Java开发,做过JVM调优吗?
做过,面试时。当然,不同职业阶段有不同的需求,面试中有面试的需求,工作中有工作需求,
面试时是必背需求,工作中是可选需求,我们维护稳定的系统时,自然不需要JVM调优,
当我们需要为新业务构建新系统或者业务发展调整系统时,为保证系统稳定运行,就需要JVM参数配置以及调优了。
调优方案,是依据当前系统资源以及业务实际情况最终确定的:减少Full GC频率。
(1)硬件资源尚有可用,可直接增加年轻代和老年代堆内存,减少Full GC频率;
(2)硬件资源不足(原因不论),调整年轻代内存分配,减少Full GC频率,依据当前业务场景,计算每次请求占用的内存,做限流处理;
(3)硬件资源不足,业务逻辑中有冗余数据生成,去除冗余数据或者调整业务需求,如减少单次数据生成量,减少Full GC频率。
2 一些理论基础
JVM自带内存回收,设计者将JVM运行时数据区的堆进一步进行划分,
一方面可以高效使用内存,另一方面灵活配置内存,适应不同的场景。
JDK8中的JVM运行时数据区堆分为老年代和年轻代,结构如下图所示:
所以我们调整的堆大小就是新生代的Eden区和老年代,
不同区的内存回收有不同的术语,如Minor GC、Major GC和Full GC。
- Minor GC:针对年轻代Eden区内存回收。当新生成的对象在Eden区无法正常分配时,会触发Minor GC,并将Eden区仍存活的对象拷贝到Survivor区,当From Survivor区达到年龄阈值的对象超过From或To Survivor空间一半时,会将这部分对象分配到老年代。
- Major GC:针对老年代内存回收。当Eden区存活的对象进入老年代或者Eden区无法分配的对象直接进入老年代,而老年代没有足够内存分配,发生Major GC。
- Full GC:针对年轻代和老年代内存回收。当老年代空间不足(直接进入老年代的新建对象)、YGC对象晋升失败(Survivor对象达到年龄设定值,进入老年代,老年代没有足够空间分配)、YGC晋升到老年代对象平均总大小大于老年代空闲空间时发生Full GC。
Full GC对整个JVM运行时数据区影响最大,不同的垃圾回收器有不同表现,如Serial垃圾回收器会发生STW(Stop The World),导致内存回收期间服务不可用,服务处于离线状态。
- 最终结论:最大限度减少Full GC频率,保证服务在线时间。
2 JVM运行时数据区:堆 观测工具
有了上面的理论基础,我们可以正确阅读【堆】运行状态以及当前JVM运行状态。
查看当前服务JVM运行时数据区堆的运行状况有多种方式,如查看GC日志、JConsole、Visula VM等工具。
本文就介绍如何使用JConsole和Visual VM查看JVM运行时数据区堆实时状态。
2.1 JConsole
JConsole是JDK提供的原生工具,安装JDK后,通过命令行即可开启JConsole,以Windows为例,
控制台输入:jconsole
默认的JConsole用户界面如下,有两种连接Java进程的方式,
直接连接本地Java进程或者通过IP和Port连接远程Java进程,本文演示连接本地Java进程。
2.1.1 老年代
连接本地Java进程后,菜单栏有概览、内存、线程、类等,我们分析JVM运行时数据区堆的使用情况,
因此查看内存菜单下信息。
通过切换图标标签,可查看不同分代的状态,如老年代。
通过老年代GC频率可以直观看出系统的稳定性,老年代GC频率高,系统不稳定性高,老年代GC频率与系统稳定性反比关系或者老年代GC频率与系统不稳定性正比关系。
这里查看老年代内存回收情况,老年代GC即曲线下降时发生,
通过老年代GC我们可以推测此时发生了Major GC或者Full GC,此时JVM运行时数据区堆老年代已无足够内存供系统使用,
老年代GC曲线拐点出现频次即发生Major GC或Full GC的频次,如果下图的波峰在单位时间(分钟或小时)内出现多次,说明系统稳定性夏下降了,需要关注系统运行情况,并进一步排查什么原因引发Major GC或Full GC的。
2.1.2 年轻代:Eden区
年轻代Eden区GC曲线如下图所示,
通过Eden区内存回收情况可以了解当前年轻代健康状况,
单位时间内Eden区回收次数较多(依实际情况而定),说明当前系统Eden区分配较小或者当前业务系统的对象朝生夕死的大对象较多,需要即时调整年轻代Eden区大小,减少Minor GC频率。
下面模拟的情况就是Eden区分配的内存较小,Minor GC频率高达:7次/分钟,这里配置的年轻代10MB,
为验证使用,年轻代内存分配不足,需要增加年轻代内存。
2.1.3 Survivor区
新生代Eden区发生GC时,最先受到影响的时Survivor区,
当Eden区内存回收时,即Mnior GC,会将存活的对象复制到Survivor From(S0)区,对象年龄+1,
再次发生Minor GC时,会将Eden区和Survivor From区区存活的对象复制到Survivor To(S1)区,
S0和S1区在发生Minor GC时依次轮转,保证MinorGC后存活的对象分配连续的内存,避免内存碎片化,
当Survivor From/To区对象达到预设的年龄或者对象超过Survivor From/To区域一半时,将这部分对象会晋升到老年代。
因此,Survivor区GC频率从侧面说明了进入老年代对象的频率,
频率越高,说明年轻代分配的内存不足或者当前业务系统对象生成逻辑存在需要优化的情况。
2.2 Visual VM
Visual VM是另一个GC可视化工具,原先是JDK的内置工具,但是从谋个JDK版本之后,Visual VM已从JDK中移除,需要单独下载使用。
Visual VM地址:https://visualvm.github.io
下载后,可执行文件:
Visual VM如何观测JVM的GC情况呢?当然有工具,Visual GC插件即可观测。
(1)安装Visula GC插件
(2)使用Visual GC插件
安装Visual GC插件后重启Visual GC,检测Java进程,通过Visual GC菜单栏即可观测当前Java进程的GC情况,
实际采集的结果如下图所示,Visual GC插件将JVM运行时数据区堆部分整合在一起方便查看。
这里分析与上面JConsole一致,不赘述。
如果想要更好的GC观测体验,推荐Visual GC。
3 小结
(1)Minor GC发生条件:Eden区被对象占满时发生,回收Eden区内存,将存活的对象复制到Survivor From(S0)区,再次发生Minor GC时,将存活的对象以及S0的对象复制到S1区,如此往复,S0与S1轮转,当S0/S1区对象满足(占用50%或者对象达到年龄阈值时)晋升到老年代:
(1.1)Eden区存活的对象大于S0或S1区50%时,直接进入老年代,不进入S0或S1区;
(1.2)S0/S1区的存活的对象超过50%,对象晋升到老年代;
(1.3)S0/S1区存活的对象年龄超过指定阈值(默认15)时,对象晋升到老年代。
(2)Major GC发生条件:老年代内存被占满时,回收老年代内存。
(3)Full GC发生条件:老年代空间不足分配新对象、YGC担保失败、YGC晋升到老年代对象平均大小大于老年代空闲空间或者显式调用System.gc,回收年轻到和老年代内存。
调优方案,是依据当前系统资源以及业务实际情况最终确定的:减少Full GC频率。
(1)硬件资源尚有可用,可直接增加年轻代和老年代堆内存,减少Full GC频率;
(2)硬件资源不足(原因不论),调整年轻代内存分配,减少Full GC频率,依据当前业务场景,计算每次请求占用的内存,做限流处理;
(3)硬件资源不足,业务逻辑中有冗余数据生成,去除冗余数据或者调整业务需求,如减少单次数据生成量,减少Full GC频率。