你好,我是李国。作为《Java 性能优化与面试 21 讲》这个课程的作者,我先来简单介绍下自己。
我曾任京东金融、陌陌科技高级架构师,工作期间,我接触的都是比较底层的中间件和操作系统,涉及大量高并发场景下的调优工作,比如缓存优化、多线程优化、JVM 调优等。因此,我在高并发下的性能优化方面积累了丰富的实践经验,同时积累了一套自己独有的优化思路和优化技巧。我曾经优化过一个运行缓慢的复杂业务,单机 QPS 由原来的 2k/s 提升到了 2w/s,整个集群 QPS 达到近 100w/s。
随着互联网的发展,高可靠、高并发以及降本增效,已成为各大公司面临的现实挑战,性能优化需求愈发迫切,大到分布式系统,小到代码块的算法优化,都已经成为你日常工作中必须要面对的事情。对于开发者而言,性能优化也从加分项变为一个热门技能,缺乏相关知识将很难在面试或工作中脱颖而出。
性能优化有哪些困扰
但是作为过来人,我发现很多学习者和实践者在 Java 性能优化上面临着很多的困扰,比如:
工作场景中遇到“性能优化”难题,往往只能靠盲猜和感觉,用临时性的补救措施去掩盖,看似解决了问题,但下次同样的问题又会发作,原因则是缺乏方法论、思路的指引,以及工具支持;
能力修炼中,由于常年接触 CRUD,缺乏高并发这一实践环境,对“性能优化”只能通过理论知识进行想象,无法认识其在工作实战中的真实面目和实操过程;
职场晋升中,只管功能开发,不了解组件设计原理,缺少深入地思考与总结,无法完成高并发、高性能系统设计这类高阶工作,难以在工作中大展拳脚,而有挑战的工作往往留给有准备的人。
总之,一旦遇到“性能优化”问题,很少人能够由点及面逆向分析,最终找到瓶颈点和优化方法,而性能优化是软件工程的深水区,也是衡量一个程序员能力高低的标准。
进行 Java 性能优化的关键
俗话说,知己知彼百战百胜,想要克服“性能优化”这一难题,先要了解性能优化的特点,并抓住其关键和本质。
作为面试必考内容,很多应聘者反映说面试官的一些问题会让其陷入模棱两可的境地,不知如何作答,比如很多人就搞不懂缓冲与缓存的区别。这种问题的答案,只能靠体系化的整理,依靠零零散散的知识是行不通的。你需要具备触类旁通的能力,才能对面试的散点知识既有深度又有广度地做进一步升华,才会让面试官眼前一亮。
性能优化是个系统性工程,对工程师的技术广度和深度都有要求。它不仅需要你精通编程语言,还需要深刻理解操作系统、JVM 以及框架原理的相互作用关系,需要你多维度、全方面地去分析排查。
此外,很多人能够遇到问题解决问题,但救火式治理只能临时补救表面问题,无法真正找出病灶,这次的解决只是为下次发作埋下了伏笔。事实上,很多性能问题往往隐藏的很深,比如,spring-aop 所引起的性能问题就比较难以排查。
再比如,有人细致到会关注 switch 语句速度快还是 if 语句快,但并不能真正解决性能问题。原因是什么呢?他虽然做了“性能优化”这个动作,但思路方向却错了。这种极细微级别的优化对性能提升的影响面是很小的;而且,细节上极度地追求性能,反而会把代码写得晦涩难懂,难以维护,导致最后舍本逐末。其实,性能优化更多要求我们关注整体效果,兼顾可靠性、扩展性,以及极端的异常场景,这样才能体现性能优化的价值。
实践比理论重要。性能优化并不是对固定、单一场景的优化,场景不同,方法也会不同。比如,如果你的业务是串行的,耗时很长,就不能简单地通过增加 CPU 资源进行性能提升;如果你的业务是并行的,也不能钻牛角尖地优化每一行代码,要照顾各个资源的协调,对短板着重进行优化,以便达到最优效果。
在过去你面临以上情况时,可能会仅凭感觉入手,或者先动手才思考,无法发现抓住本质,但在本课程中,我会向你讲解正确的思路,让你进行性能优化时有理可依。
课程设计
在这个课程中,我汇总了 Java 性能优化的经典案例,结合大量代码示例,尽力为你还原真实的业务场景。
课程分为 5 个模块,共 21 篇,我将从理论分析、工具支持、案例与面试点,以及 JVM 优化四大方面展开系统讲解:
模块一:理论分析,针对平常对性能优化的盲猜问题,我们会首先讲解大量的衡量指标,然后以此为依据,盘点一下常用的优化方法,包括业务优化、复用优化、计算优化、结果集优化、资源冲突优化、算法优化、高效实现等方面。学完后,你将会了解如何描述性能,并对性能优化有个整体的印象。
模块二:工具支持,工欲善其事,必先利其器。此部分将介绍一些评估操作系统设备性能的工具,包含大量实用的命令行解析;还会介绍 Java 中最有效的基准测试工具 JMH,以及一些监测 JVM 性能的应用。本模块的目的,是为大家提供一些测量性能的工具,为实践环节做准备。
模块三:实战案例与高频面试点,该模块为课程的主要内容,结合之前模块的理论分析和工具支持,通过海量实战案例,深入专项性能场景,并将每个场景下的高频面试点逐一击破,点拨调优思路,目标是能够做到举一反三,在遇到相似的性能问题时,能够快速想到合适的切入点进行优化。
模块四:JVM 优化,该模块对系统的性能提升是巨大的。本部分主要介绍垃圾回收的一些基本知识,看一下 JIT 在性能提升上所做的文章;最后列举了一些常见的优化参数,以及对编码方面的要求。学完本模块,你将掌握和 JVM 相关的常见优化措施。
模块五:特别放送,最后,针对工作中最常用的服务和框架,我想和你介绍一个 SpringBoot 服务的优化案例,涵盖 Tomcat、Undertow、JVM、网络等场景,同时再进行优化方法和求职面经总结,希望以一个全局的案例,帮助你掌握从系统层到应用层的整个优化技巧。
你将收获
建立完整的性能优化知识体系。你可以系统地学习相关知识,而不是碎片化获取,基础理论实用性强,直入主题,让你在工作实战时有理可依,有据可循。
能够对线上应用输出优化思路。掌握各种实战排查工具,并灵活应用,定位至应用中的症结瓶颈点,并输出优化思路方案。正确的方法比努力更重要,有了正确的思路方法,才能在实际工作中避免跑偏,避免把大力气花在一些细枝末节上。我还会分享大量的操作系统方面的知识,让你对应用性能有更好的评测。
收获海量实战经验分享。作为这门课最硬核内容,我将从流行的中间件介绍到常用的工具类,再到 JDK 中的知识点,用实战分析和经验分享高度还原真实的业务场景,带你了解性能优化的全过程。
获得面试 Offer 收割利器。本课程的大多数案例,都是 Java 面试题的重灾区,我将直接指出高频考点,让你既能在整体上对性能优化提供建议,也能深入细节进行针对性优化。
讲师寄语
最后,性能优化既是工程师们进阶的“拦路虎”,也可以是你能力的炼金石。希望这个专栏可以让 这个非常难啃的老大难问题,变得“平易近人”“通俗易懂”“一点就通”,希望可以让你体会到“哦,原来如此简单!”的感觉,体会到久违的学习的快乐,并能学有所用。
另外,我去年就与拉勾教育平台合作了《深入浅出 Java 虚拟机》(已完结)课程,用户口碑还不错,Java 虚拟机这门课可作为 Java 性能优化课程的一个补充,我也推荐你去学习了解。
Java 性能优化对知识广度和知识深度都有比较高的要求,让我们掌握性能调优的思路,多多实践,使自己的编码水平更上层楼。
本课时主要从理论分析入手来介绍性能优化的衡量指标,及其理论方法和注意点。
指标是我们衡量很多事物,以及做出行为决策的重要参考。例如在生活中,当你打算买汽车时,会关注很多指标,比如动力性、燃油经济性、制动性、操纵稳定性、平顺性、通过性、排放与噪声等,而这些指标也都有相关的测试和参数,同时也会对这些指标进行一一参考。
这个道理大家都懂,但一旦到了性能优化上,却往往因为缺乏理论依据而选择了错误的优化方向,陷入了盲猜的窘境。在衡量一项优化是否能达到目的之时,不能仅靠感觉,它同样有一系列的指标来衡量你的改进。如果在改动之后,性能不升反降,那就不能叫性能优化了。
所谓性能,就是使用有限的资源在有限的时间内完成工作。最主要的衡量因素就是时间,所以很多衡量指标,都可以把时间作为横轴。
加载缓慢的网站,会受到搜索排名算法的惩罚,从而导致网站排名下降。 因此加载的快慢是性能优化是否合理的一个非常直观的判断因素,但性能指标不仅仅包括单次请求的速度,它还包含更多因素。
接下来看一下,都有哪些衡量指标能够帮我们进行决策。
衡量指标有哪些?
1. 吞吐量和响应速度
分布式的高并发应用并不能把单次请求作为判断依据,它往往是一个统计结果。其中最常用的衡量指标就是吞吐量和响应速度,而这两者也是考虑性能时非常重要的概念。要理解这两个指标的意义,我们可以类比为交通环境中的十字路口。
在交通非常繁忙的情况下,十字路口是典型的瓶颈点,当红绿灯放行时间非常长时,后面往往会排起长队。
从我们开车开始排队,到车经过红绿灯,这个过程所花费的时间,就是响应时间。
当然,我们可以适当地调低红绿灯的间隔时间,这样对于某些车辆来说,通过时间可能会短一些。但是,如果信号灯频繁切换,反而会导致单位时间内通过的车辆减少,换一个角度,我们也可以认为这个十字路口的车辆吞吐量减少了。
像我们平常开发中经常提到的,QPS 代表每秒查询的数量,TPS 代表每秒事务的数量,HPS 代表每秒的 HTTP 请求数量等,这都是常用的与吞吐量相关的量化指标。
在性能优化的时候,我们要搞清楚优化的目标,到底是吞吐量还是响应速度。 有些时候,虽然响应速度比较慢,但整个吞吐量却非常高,比如一些数据库的批量操作、一些缓冲区的合并等。虽然信息的延迟增加了,但如果我们的目标就是吞吐量,那么这显然也可以算是比较大的性能提升。
一般情况下,我们认为:
响应速度是串行执行的优化,通过优化执行步骤解决问题;
吞吐量是并行执行的优化,通过合理利用计算资源达到目标。
我们平常的优化主要侧重于响应速度,因为一旦响应速度提升了,那么整个吞吐量自然也会跟着提升。
但对于高并发的互联网应用来说,响应速度和吞吐量两者都需要。这些应用都会标榜为高吞吐、高并发的场景,用户对系统的延迟忍耐度很差,我们需要使用有限的硬件资源,从中找到一个平衡点。
2. 响应时间衡量
既然响应时间这么重要,我们就着重看一下响应时间的衡量方法。
(1)平均响应时间
我们最常用的指标,即平均响应时间(AVG),该指标能够体现服务接口的平均处理能力。它的本质是把所有的请求耗时加起来,然后除以请求的次数。举个最简单的例子,有 10 个请求,其中有 2 个 1ms、3 个 5ms、5 个 10ms,那么它的平均耗时就是(2*1+3*5+5*10)/10=6.7ms。
除非服务在一段时间内出现了严重的问题,否则平均响应时间都会比较平缓。因为高并发应用请求量都特别大,所以长尾请求的影响会被很快平均,导致很多用户的请求变慢,但这不能体现在平均耗时指标中。
为了解决这个问题,另外一个比较常用的指标,就是百分位数(Percentile)。
(2)百分位数
这个也比较好理解。我们圈定一个时间范围,把每次请求的耗时加入一个列表中,然后按照从小到大的顺序将这些时间进行排序。这样,我们取出特定百分位的耗时,这个数字就是 TP 值。可以看到,TP 值(Top Percentile)和中位数、平均数等是类似的,都是一个统计学里的术语。
它的意义是,超过 N% 的请求都在 X 时间内返回。比如 TP90 = 50ms,意思是超过 90th 的请求,都在 50ms 内返回。
这个指标也是非常重要的,它能够反映出应用接口的整体响应情况。比如,某段时间若发生了长时间的 GC,那它的某个时间段之上的指标就会产生严重的抖动,但一些低百分位的数值却很少有变化。
我们一般分为 TP50、TP90、TP95、TP99、TP99.9 等多个段,对高百分位的值要求越高,对系统响应能力的稳定性要求越高。
在这些高稳定性系统中,目标就是要干掉严重影响系统的长尾请求。这部分接口性能数据的收集,我们会采用更加详细的日志记录方式,而不仅仅靠指标。比如,我们将某个接口,耗时超过 1s 的入参及执行步骤,详细地输出在日志系统中。
3. 并发量
并发量是指系统同时能处理的请求数量,这个指标反映了系统的负载能力。
在高并发应用中,仅仅高吞吐是不够的,它还必须同时能为多个用户提供服务。并发高时,会导致很严重的共享资源争用问题,我们需要减少资源冲突,以及长时间占用资源的行为。
针对响应时间进行设计,一般来说是万能的。因为响应时间减少,同一时间能够处理的请求必然会增加。值得注意的是,即使是一个秒杀系统,经过层层过滤处理,最终到达某个节点的并发数,大概也就五六十左右。我们在平常的设计中,除非并发量特别低,否则都不需要太过度关注这个指标。
4. 秒开率
在移动互联网时代,尤其对于 App 中的页面,秒开是一种极佳的用户体验。如果能在 1 秒内加载完成页面,那用户可以获得流畅的体验,并且不会产生更多的焦虑感。
通常而言,可以根据业务情况设定不同的页面打开标准,比如低于 1 秒内的数据占比是秒开率。业界优秀的公司,比如手淘,其页面的秒开率基本可达到 80% 以上。
5. 正确性
说一个比较有意思的事情。我们有个技术团队,在进行测试的时候,发现接口响应非常流畅,把并发数增加到 20 以后,应用接口响应依旧非常迅速。
但等应用真正上线时,却发生了重大事故,这是因为接口返回的都是无法使用的数据。
其问题原因也比较好定位,就是项目中使用了熔断。在压测的时候,接口直接超出服务能力,触发熔断了,但是压测并没有对接口响应的正确性做判断,造成了非常低级的错误。
所以在进行性能评估的时候,不要忘记正确性这一关键要素。
有哪些理论方法?
性能优化有很多理论方法,比如木桶理论、基础测试、Amdahl 定律等。下面我们简单地讲解一下最常用的两个理论。
1. 木桶理论
一只木桶若想要装最多的水,则需要每块木板都一样长而且没有破损才行。如果有一块木板不满足条件,那么这只桶就无法装最多的水。
能够装多少水,取决于最短的那块木板,而不是最长的那一块。
木桶效应在解释系统性能上,也非常适合。组成系统的组件,在速度上是良莠不齐的。系统的整体性能,就取决于系统中最慢的组件。
比如,在数据库应用中,制约性能最严重的是落盘的 I/O 问题,也就是说,硬盘是这个场景下的短板,我们首要的任务就是补齐这个短板。
2. 基准测试、预热
基准测试(Benchmark)并不是简单的性能测试,是用来测试某个程序的最佳性能。
应用接口往往在刚启动后都有短暂的超时。在测试之前,我们需要对应用进行预热,消除 JIT 编译器等因素的影响。而在 Java 里就有一个组件,即 JMH,就可以消除这些差异。
注意点
1. 依据数字而不是猜想
有些同学对编程有很好的感觉,能够靠猜测列出系统的瓶颈点,这种情况固然存在,但却非常不可取。复杂的系统往往有多个影响因素,我们应将性能分析放在第一位,把性能优化放在次要位置,直觉只是我们的辅助,但不能作为下结论的工具。
进行性能优化时,我们一般会把分析后的结果排一个优先级(根据难度和影响程度),从大处着手,首先击破影响最大的点,然后将其他影响因素逐一击破。
有些优化会引入新的性能问题,有时候这些新问题会引起更严重的性能下降,你需要评估这个连锁反应,确保这种优化确实需要,同时需要使用数字去衡量这个过程,而不是靠感觉猜想。
2. 个体数据不足信
你是否有这样的经历:某个知名网站的访问速度真慢,光加载就花费了 x 秒。其实,仅凭一个人的一次请求,就下了“慢”这个结论,是不合适的,而在我们进行性能评估的时候,也往往会陷入这样的误区。
这是因为个体请求的小批量数据,可参考价值并不是非常大。响应时间可能因用户的数据而异,也可能取决于设备和网络条件。
合理的做法,是从统计数据中找到一些规律,比如上面所提到的平均响应时间、TP 值等,甚至是响应时间分布的直方图,这些都能够帮我们评估性能质量。
3. 不要过早优化和过度优化
虽然性能优化有这么多好处,但并不代表我们要把每个地方都做到极致,性能优化也是要有限度的。程序要运行地正确,要比程序运行得更快还要困难。
计算机科学的鼻祖"Donald Knuth" 曾说:“过早的优化是万恶之源”,就是这个道理。
如果一项改进并不能产生明显的价值,那我们为什么还要花大力气耗在上面呢?比如,某个应用已经满足了用户的吞吐量需求和响应需求,但有的同学热衷于 JVM 的调优,依然花很大力气在参数测试上,这种优化就属于过度优化。
时间要花在刀刃上,我们需要找到最迫切需要解决的性能点,然后将其击破。比如,一个系统主要是慢在了数据库查询上,结果你却花了很大的精力去优化 Java 编码规范,这就是偏离目标的典型情况。
一般地,性能优化后的代码,由于太过于追求执行速度,读起来都比较晦涩,在结构上也会有很多让步。很显然,过早优化会让这种难以维护的特性过早介入到你的项目中,等代码重构的时候,就会花更大的力气去解决它。
正确的做法是,项目开发和性能优化,应该作为两个独立的步骤进行,要做性能优化,要等到整个项目的架构和功能大体进入稳定状态时再进行。
4. 保持良好的编码习惯
我们上面提到,不要过早地优化和过度优化,但并不代表大家在编码时就不考虑这些问题。
比如,保持好的编码规范,就可以非常方便地进行代码重构;使用合适的设计模式,合理的划分模块,就可以针对性能问题和结构问题进行聚焦、优化。
在追求高性能、高质量编码的过程中,一些好的习惯都会积累下来,形成人生道路上优秀的修养和品质,这对我们是大有裨益的。
小结
在本课时,我们简单地了解了衡量性能的一些指标,比如常见的吞吐量和响应速度,还探讨了一些其他的影响因素,比如并发量、秒开率、容错率等。
同时,我们也谈到了木桶理论和基准测试等两种过程方法,并对性能测试中的一些误区和注意点进行了介绍,现在你应该对如何描述性能有了更好的理解。像一些专业的性能测试软件,如 JMeter、LoadRunner 等,就是在这些基础性能指标上进行的扩展。我们在平常的工作中,也应该尽量使用专业术语,这样才能对系统性能进行正确评估。
了解了优化指标后,有了行动导向,那接下来该从哪些方面入手呢? Java 性能优化是否有可以遵循的规律呢?
下一课时,我们将从整体上,来介绍一下性能优化都有哪些考量点。
精选评论
*众:
老师讲的很不错
**霖:
很少看到这么系统讲解性能优化的,老师的独到见解真是的实在的东西。收获满满
**也:
期待
**千:
目前来看,这个课不错
*浩:
希望后边老师能结合一些实战案例进行讲解😁
编辑回复:
之后的课程中会有很多案例哦,并且还对讲解对应的高频考点
Rexza:
老师很厉害,课程很棒
**丰:
希望跟着大佬,慢慢让自己上一个台阶,🙏
**鑫:
结合实践例子多讲一些😀😀
编辑回复:
后面的课时会有很多实战案例哦,并且还会讲解对应的考点
**文:
讲的挺好
**哈:
言简意赅。看过很多文章,这是把响应时间 ,吞吐量,并发量解释最通俗易懂的,给老师点赞,给拉勾点赞。
**达:
性能优化光看书效果不大,跟对老师了
**强:
课程会会过期吗?
编辑回复:
不会,是永久的哦~
Rexza:
加油加油,一起学习进步努力
Rexza:
讲的很深入很全面,希望自己可以在性能优化方面进步多多😊
**0107:
额嗯~咱们这个课程多久更新一讲呢,喜欢
编辑回复:
每周一、周三更新,每次更新一讲~O(∩_∩)O
*洋:
学习了,期待后面能更深入、易懂的细节。
**1463:
很期待老师的教学
编辑回复:
O(∩_∩)O