前言
首先给大家说声对不起,最近属实太忙了,白天上班,晚上加班,回家还要收拾家里,基本每天做完所有事儿都是凌晨一两点了,没有精力再搞其他的了.
好了,进入正题,让我们来聊聊JVM篇最后一个章节----JVM性能调优.童鞋们随便打开一个大厂的招聘岗位JD,应该都会有JVM调优相关的描述,其实招聘方不一定要求候选人真的对JVM调优有实际调优经验,但是至少得有思路,知道应该怎样进行JVM层面的性能调优,说实话,知道如何进行JVM层面的性能调优的人,在面试中确实是有加分的.
笔者在公司担任面试官的时候,经常会看到候选人简历描述有JVM性能调优经验,每当这个时候我都会问候选人一个问题,你是如何进行JVM性能调优的,很多童鞋的回答就是:噢,就是调整一下初始堆大小,新生代大小.这明显不是笔者想要的答案,因为这根本就不叫JVM性能调优.童鞋们对号入座一下,对JVM调优仅仅是我上述说的那样的,赶紧改一下简历,不要说自己会JVM性能调优.说实话,对JVM进行性能调优是对架构师的要求,甚至我敢说很多架构师都不一定有实际的JVM性能调优经验.话不多说,让我们进入正题,我们将从以下几点来讲解如何进行性能调优:
- JVM性能调优的前提
- JVM性能调优的预备知识
- STW现象--Stop-The-World
- 垃圾回收器的种类
- 性能调优的目的
- 如何进行性能调优
JVM性能调优的前提
所有有经验的架构师一定会有一个共识,JVM层面的性能调优一定是作为最后的调优手段,在此之前,一定要确保系统其它方面都已经做到了极致,无法再进行调优了,在这个前提下,才会考虑JVM性能调优.这里的其他方面包括从前端到架构到代码层面,我举一些例子
从浏览器/APP角度可进行的优化有:
- 减少HTTP请求次数.
- 使用客户端缓存.
- 浏览器启用压缩
- 使用CDN加速
- 动态资源和静态资源分离
从系统层面可进行的优化有:
- 启用缓存,比如redis缓存数据
- 使用集群
- 异步处理,比如引入消息队列
- 对代码的优化
以上列出的每一点,都能单独拎出来讲很久,但是博文篇幅问题,就不细说了,有兴趣的童鞋可以自行下去了解,我们只着重说一下代码优化,相信大家都知道,业界比较公认的编码规范是阿里巴巴发布的<java开发手册>,里面的内容都是阿里集团多年来血的教训积累的精华,阿里集团内部有一个组织专门负责手册的编写和推广,并且在不断进行优化,最新版发布到泰山版了(我看的时候还是华山版,哈哈).如果有童鞋还不知道的,我建议去下载下来看一看.另外阿里云上有<java开发手册>的考试,如果通过了这个考试,说明你的编码规范还是不错的,有兴趣的童鞋可以去试一下.
除此之外,童鞋们还应该了解JVM本身为我们悄悄做的各种优化,其中最重要的是JIT编译器的优化.我举几个例子,比如:方法内联,逃逸分析等.默认情况下,这些都是开启的,如果不开启这些功能,JVM性能会下降50%以上.除此之外,还有一些比如:栈上分配,TLAB等优化.这些内容由于平时我们开发中不会用到,是JVM在背后悄悄为我们做了优化,因此可能很多童鞋都不知道,但是如果想成为一个合格的架构师,这些内容都是必须要知道的,毕竟架构师的知识广度和深度决定了架构师的高度.
除此之外,性能调优一定是基于性能测试的,空口说进行性能调优的都是耍流氓,只有在经过了实际的性能测试后,我们才知道系统的瓶颈在哪里,才知道那些方面需要进行调优,如何进行调优.常用的性能测试指标有TPS/QPS/吞吐量.并且默认所有的接口访问都遵循二八原则(接口每天80%的访问量集中在20%的时间内).
JVM性能调优的预备知识
在进行JVM性能调优之前,我们还得了解JVM,比如我前面的几篇有关JVM的博文,都是需要掌握的,比如JVM内存模型,垃圾回收机制等等.另外我们还需要掌握一些进行JVM分析的工具.其实在我们安装JDK的时候,JDK已经为我们准备了许多有用的性能调优监控工具,我们可以看一下JDK安装目录下的bin目录:
- jps: 主要用来输出JVM中运行的进程状态信息
- jstack: 主要用来查看某个Java进程内的线程堆栈信息
- jmap: 用来查看堆内存使用状况,一般结合jhat使用
- jstat: JVM统计监测工具
- jconsole: 图形化的统计工具
- jvisualvm: 比jconsole功能更强的图形化的监控工具
另外我们需要知道,当JVM发生OOM异常时,可以使用命令生成一个.hprof后缀的dump文件,该文件是某个时间节点的heap的快照,我们可以使用VisualVM来查看,也可以用第三方提供的一些工具,比如eclipse的MAT来查看内存溢出的原因.
STW现象
所谓STW现象(Stop-The-World)是指在执行垃圾收集算法时,Java应用程序的其他所有线程(除了垃圾收集线程之外的线程)都被挂起.此时,系统只允许GC线程继续运行,其他线程全部暂停,等待GC线程执行完毕后才能继续执行.这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停掉,举个例子,某个接口平时可能只需要50ms的RT,忽然某次调用花费了200ms.因此STW对实时性要求很高的系统来说是难以接受的.
垃圾回收器的种类
既然有STW现象,那么有没有解决方案呢?这就是我们接下来要讲的,垃圾回收器的种类,目前为止,JVM一共为我们提供了七种垃圾回收器,其中年轻代有三种,老年代有三种,另外还有一种特殊的G1:
当然,在更新版本的JDK中还有一种ZGC,为未来垃圾回收器提供了一种趋势,有兴趣的童鞋可以自行了解.
性能调优的目的和具体过程
有了前面的铺垫,终于来到了我们最重要的正题:如何进行JVM性能调优?在我看来JVM调优的具体步骤分为如下几步:
- 确定调优的目的,选择合适的GC collector
- 调整JVM heap的大小
- 调整young generation在整个JVM heap中所占的比重.
确定调优的目的,选择合适的GC collector
由于每种垃圾回收器的特性并不相同,因此我们需要根据我们的调优目的选择合适的垃圾回收器,比如,我们需要降低STW的停顿时间,那我们就不能选用串行和并行的垃圾回收器,而应该选用并发的垃圾回收器,即CMS,与之搭配的新生代垃圾回收器就应该选用ParNew.目前一般主流互联网公司都是用CMS垃圾回收器.
调整JVM heap的大小
确定了垃圾回收器的类型,就需要调整JVM heap的大小,在这一步的时候,首先我们需要了解JVM相关的一些指令,比如可以在启动java程序时加上
-XX:+HeapDumpOnOutOfMemoryError,当发生OOM时,JVM会自动为我们生成DUMP文件
-XX:+PrintGCDetails -Xloggc:D:\gclogger\gc.log -XX:+PrintGCDateStamps 生成GC日志
-Xms2g -Xmx2g 当然,还应该要根据实际情况设置heap的最大最小值,童鞋们要知道,默认情况下,java程序启动的最小heap大小为1/64物理内存,最大值为1/4物理内存,一般要求我们最大最小值保持一致,避免JVM频繁扩容和缩容导致不必要的性能浪费.
调整young generation在整个JVM heap中所占的比重.
确定了heap的大小,还需要确定新生代的比重
–Xmn1500m -XX:MetaspaceSize=150M
当然,再厉害的架构师也不可能一次就调整得出最佳的JVM配置参数,而是应该多设置几组不同的值,放到生产环境(或者和生产环境一样的环境,比如阿里内部有预发环境,和生产环境保持一致)进行性能测试,通过对比结果得出最佳的JVM性能调优参数,完成JVM性能调优.
总结
读完了本篇文章,我相信童鞋们应该或多或少会有些收获.掌握JVM调整的核心步骤:
- 确定调优的目的,选择合适的GC collector
- 调整JVM heap的大小
- 调整young generation在整个JVM heap中所占的比重.
不了解如何进行JVM的调优的人,把本文内容好好理解后,能让面试官刮目相看;了解JVM调优,但是条理不清晰的童鞋,可能会对JVM调优有更清晰的认识.总而言之,笔者认为本文是一篇满满的干货,网上许多讲JVM调优的博文,并没有这么系统的讲解过真正应该如何进行JVM调优.当然笔者能力有限,如文章有错误,欢迎指正,毕竟是人就会犯错.
PS:一入JVM深似海,从此再也出不来.对JVM有兴趣的童鞋,可以钻研,但是在经验不足之前,不建议太过深入了解JVM,否则会耽误自己.当然,立志要成为一名牛逼的架构师,这些都是必须要会的.
本文我们讲完了JVM,下一篇开始,让我们继续学习JAVA锁相关的内容.
如果觉得博主写的不错,欢迎关注博主微信公众号,博主会不定期分享技术干货!
本文由博客一文多发平台 OpenWrite 发布!