11 个简单的 Java 性能调优技巧
大多数开发人员理所当然地以为性能优化很复杂,需要大量的经验和知识。好吧,不能说这是完全错误的。优化应用程序以获得最佳性能不是一件容易的事情。但是,这并不意味着如果你不具备这些知识,就不能做任何事情。这里有11个易于遵循的建议和最佳实践可以帮助你创建一个性能良好的应用程序。
大部分建议是针对Java的。但也有若干建议是与语言无关的,可以应用于所有应用程序和编程语言。在讨论专门针对Java的性能调优技巧之前,让我们先来看看通用技巧。
1.在你知道必要之前不要优化
这可能是最重要的性能调整技巧之一。你应该遵循常见的最佳实践做法并尝试高效地实现用例。但是,这并不意味着在你证明必要之前,你应该更换任何标准库或构建复杂的优化。
在大多数情况下,过早优化不但会占用大量时间,而且会使代码变得难以阅读和维护。更糟糕的是,这些优化通常不会带来任何好处,因为你花费大量时间来优化的是应用程序的非关键部分。
那么,你如何证明你需要优化一些东西呢?
首先,你需要定义应用程序代码的速度得多快,例如,为所有API调用指定最大响应时间,或者指定在特定时间范围内要导入的记录数量。在完成这些之后,你就可以测量应用程序的哪些部分太慢需要改进。然后,接着看第二个技巧。
2.使用分析器查找真正的瓶颈
在你遵循第一个建议并确定了应用程序的某些部分需要改进后,那么从哪里开始呢?
你可以用两种方法来解决问题:
查看你的代码,并从看起来可疑或者你觉得可能会产生问题的部分开始。
或者使用分析器并获取有关代码每个部分的行为和性能的详细信息。
希望不需要我解释为什么应该始终遵循第二种方法的原因。
很明显,基于分析器的方法可以让你更好地理解代码的性能影响,并使你能够专注于最关键的部分。如果你曾使用过分析器,那么你一定记得曾经你是多么惊讶于一下就找到了代码的哪些部分产生了性能问题。老实说,我第一次的猜测不止一次地导致我走错了方向。
3.为整个应用程序创建性能测试套件
这是另一个通用技巧,可以帮助你避免在将性能改进部署到生产后经常会发生的许多意外问题。你应该总是定义一个测试整个应用程序的性能测试套件,并在性能改进之前和之后运行它。
这些额外的测试运行将帮助你识别更改的功能和性能副作用,并确保不会导致弊大于利的更新。如果你工作于被应用程序若干不同部分使用的组件,如数据库或缓存,那么这一点就尤其重要。
4.首先处理最大的瓶颈
在创建测试套件并使用分析器分析应用程序之后,你可以列出一系列需要解决以提高性能的问题。这很好,但它仍然不能回答你应该从哪里开始的问题。你可以专注于速效方案,或从最重要的问题开始。
速效方案一开始可能会很有吸引力,因为你可以很快显示第一个成果。但有时,可能需要你说服其他团队成员或管理层认为性能分析是值得的——因为暂时看不到效果。
但总的来说,我建议首先处理最重要的性能问题。这将为你提供最大的性能改进,而且可能再也不需要去解决其中一些为了满足性能需求的问题。
常见的性能调整技巧到此结束。下面让我们仔细看看一些特定于Java的技巧。
5.使用StringBuilder以编程方式连接String
有很多不同的选项来连接Java中的String。例如,你可以使用简单的+或+ =,以及StringBuffer或StringBuilder。
那么,你应该选择哪种方法?
答案取决于连接String的代码。如果你是以编程方式添加新内容到String中,例如在for循环中,那么你应该使用StringBuilder。它很容易使用,并提供比StringBuffer更好的性能。但请记住,与StringBuffer相比,StringBuilder不是线程安全的,可能不适合所有用例。
你只需要实例化一个新的StringBuilder并调用append方法来向String中添加一个新的部分。在你添加了所有的部分之后,你就可以调用toString()方法来检索连接的String。
下面的代码片段显示了一个简单的例子。在每次迭代期间,这个循环将i转换为一个String,并将它与一个空格一起添加到StringBuilder sb中。所以,最后,这段代码将在日志文件中写入“This is a test0 1 2 3 4 5 6 7 8 9”。
StringBuilder sb =newStringBuilder(“Thisis a test”);for(inti=0; i<10; i++) {sb.append(i);sb.append(” “);}log.info(sb.toString());
正如在代码片段中看到的那样,你可以将String的第一个元素提供给构造方法。这将创建一个新的StringBuilder,新的StringBuilder包含提供的String和16个额外字符的容量。当你向StringBuilder添加更多字符时,JVM将动态增加StringBuilder的大小。
如果你已经知道你的String将包含多少个字符,则可以将该数字提供给不同的构造方法以实例化具有定义容量的StringBuilder。这进一步提高了效率,因为它不需要动态扩展其容量。
6.使用+连接一个语句中的String
当你用Java实现你的第一个应用程序时,可能有人告诉过你不应该用+来连接String。如果你是在应用程序逻辑中连接字符串,这是正确的。字符串是不可变的,每个字符串的连接结果都存储在一个新的String对象中。这需要额外的内存,会减慢你的应用程序,特别是如果你在一个循环内连接多个字符串的话。
在这些情况下,你应该遵循技巧5并使用StringBuilder。
但是,如果你只是将字符串分成多行来改善代码的可读性,那情况就不一样了。
Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);
在这些情况下,你应该用一个简单的+来连接你的字符串。Java编译器会对此优化并在编译时执行连接。所以,在运行时,你的代码将只使用1个String,不需要连接。
7.尽可能使用基元
避免任何开销并提高应用程序性能的另一个简便而快速的方法是使用基本类型而不是其包装类。所以,最好使用int来代替Integer,使用double来代替Double。这允许JVM将值存储在堆栈而不是堆中以减少内存消耗,并作出更有效的处理。
8.试着避免BigInteger和BigDecimal
既然我们在讨论数据类型,那么我们也快速浏览一下BigInteger和BigDecimal吧。尤其是后者因其精确性而受到大家的欢迎。但是这是有代价的。
BigInteger和BigDecimal比简单的long或double需要更多的内存,并且会显著减慢所有计算。所以,你如果需要额外的精度,或者数字将超过long的范围,那么最好三思而后行。这可能是你需要更改以解决性能问题的唯一方法,特别是在实现数学算法的时候。
9.首先检查当前日志级别
这个建议应该是显而易见的,但不幸的是,很多程序员在写代码的时候都会大多会忽略它。在你创建调试消息之前,始终应该首先检查当前日志级别。否则,你可能会创建一个之后会被忽略的日志消息字符串。
这里有两个反面例子。
// don’t do thislog.debug(“User[” + userName+ “] called method X with [” + i + “]”);//orthislog.debug(String.format(“User[%s] called method X with [%d]”, userName, i));
在上面两种情况中,你都将执行创建日志消息所有必需的步骤,在不知道日志框架是否将使用日志消息的前提下。因此在创建调试消息之前,最好先检查当前的日志级别。
//dothisif (log.isDebugEnabled()) {log.debug(“User [” + userName + “] called method Xwith[” + i + “]”);}
10.使用Apache Commons StringUtils.Replace而不是String.replace
一般来说,String.replace方法工作正常,效率很高,尤其是在使用Java 9的情况下。但是,如果你的应用程序需要大量的替换操作,并且没有更新到最新的Java版本,那么我们依然有必要查找更快和更有效的替代品。
有一个备选答案是Apache Commons Lang的StringUtils.replace方法。正如Lukas Eder在他最近的一篇博客文章中所描述的,StringUtils.replace方法远胜Java 8的String.replace方法。
而且它只需要很小的改动。即添加Apache Commons Lang项目的Maven依赖项到应用程序pom.xml中,并将String.replace方法的所有调用替换为StringUtils.replace方法。
// replace thistest.replace(“test”, “simpletest”);// with thisStringUtils.replace(test, “test”, “simpletest”);
11.缓存昂贵的资源,如数据库连接
缓存是避免重复执行昂贵或常用代码片段的流行解决方案。总的思路很简单:重复使用这些资源比反复创建新的资源要便宜。
一个典型的例子是缓存池中的数据库连接。新连接的创建需要时间,如果你重用现有连接,则可以避免这种情况。
你还可以在Java语言本身找到其他例子。例如,Integer类的valueOf方法缓存了-128到127之间的值。你可能会说创建一个新的Integer并不是太昂贵,但是由于它经常被使用,以至于缓存最常用的值也可以提供性能优势。
但是,当你考虑缓存时,请记住缓存实现也会产生开销。你需要花费额外的内存来存储可重用资源,因此你可能需要管理缓存以使资源可访问,以及删除过时的资源。
所以,在开始缓存任何资源之前,请确保实施缓存是值得的,也就是说必须足够多地使用它们。
如果想学习Java性能优化,工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加下454377428群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
总结
正如你所看到的,有时不需要太多工作就可以提高应用程序的性能。本文中的大部分建议只需要你稍作努力就可以将它们应用于你的代码。
但是,最重要的还是那些与是什么编程语言无关的技巧:
在你知道必要之前不要优化
使用分析器查找真正的瓶颈
首先处理最大的瓶颈
java开发人员可以看过来。
对于参加工作1年到2年的同学。这部分时间段的同学,已经对Java有了一个更加深入的了解。
对于参加工作2年到3年的同学有的同学在这个时候觉得自己已经很牛逼了,于是忍不住开始慢慢松懈。
参加工作3年到4年的同学这个阶段的同学,提升已经是很难了,而且这个阶段的学习往往会比较多样化。
参加工作4年到5年的同学经过前面一年的历练,相信你在自己所钻研的领域已经有了自己一定的见解,这个时候,技术上你应该已经遇到瓶颈了。
可以一起学习:454377428 java架构\多线程\高性能 一起交流学习吧、 帮你提升自己,图片瓶颈,跟上时代的脚步。
为大家分享自己总结的架构师学习路线图,大家可以拿来做一个参考:
一、分布式架构体系
分布式怎么来的。传统的电信、银行业,当业务量大了之后,普通服务器CPU/IO/网络到了100%,请求太慢怎么办?最直接的做法,升级硬件,反正也不缺钱,IBM小型机,大型机,采购了堆硬件。
但是互联网不能这么干,互联网没有那么财大气粗,还有很多初创,能不能赚钱还不知道。所以就有了软件方面的解决方案:分布式系统,简单说,就是一台服务器不行,我用两台、10台、100台...这就要软件系统需要支持。
那么多台机器,我如何让他们协同工作,这就需要一个调度中心(或注册中心);肯定涉及到机器间通信,那么需要一个高效的RPC框架;一个请求过来了,如何分发,需要一个请求分发系统(负载均衡);然后还要考虑每个角色都不能成为性能瓶颈;还有要能方便的进行横向扩展,还有考虑单节点故障。
需要分布式系统,并发量肯定不低,
那么有了上面的还是不够的,还需要考虑cache、mq、job、db等方面的问题。cache,现在第三方缓存也比较成熟,redis/memcache等;mq,rabbitmq,kafka等等也不错;job,现在第三方任务框架有elasticjob和tbschedule,或者你用quartz也支持分布式环境下的任务,不过quartz就没有运维工具了。DB,数据库最好在项目前期就考虑好业务拆分,系统拆分后DB对应的垂直拆分,后期可做读写分离,一主多从,甚至多主多从,业界也有了相应的解决方案。
总结一下,首先要了解分布式原理,然后对应着每个功能区找业界内成熟的产品来实时。互联网行业,基本都有开源的产品供你选择。
下图是我总结的分布式的技术攻克点:
二、微服务架构
微服务(Microservice)这个概念是2012年出现的,作为加快Web和移动应用程序开发进程的一种方法,2014年开始受到各方的关注,而2015年,可以说是微服务的元年;
越来越多的论坛、社区、blog以及互联网行业巨头开始对微服务进行讨论、实践,可以说这样更近一步推动了微服务的发展和创新。
微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。你可以将其看作是在架构层次而非获取服务的
类上应用很多SOLID原则。微服务架构是个很有趣的概念,它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。
概念:把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。
定义:围绕业务领域组件来创建应用,这些应用可独立地进行开发、管理和迭代。在分散的组件中使用云架构和平台式部署、管理和服务功能,使产品交付变得更加简单。
本质:用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。
这些知识点都是我从业多年总结出来的的经验,都是当前最主流的技术。想学习这些技术的朋友可以加群:454377428。群里会分享这些技术知识点供大家学习免费下载
下图是我总结的微服务的技术要点:
三、阅读源码、分析源码
程序员每天都和代码打交道。经过数年的基础教育和职业培训,大部分程序员都会「写」代码,或者至少会抄代码和改代码。但是,会读代码的并不在多数,会读代码又真正读懂一些大项目的源码的,少之又少。这种怪状,真要追究起来,怪不得程序员这个群体本身 —— 它是两个原因造成的。
我们所有的教育和培训都在强调怎么写代码,并没有教大家如何读代码
大多数工作场景都是一个萝卜一个坑,我们只需要了解一个系统的局部便能开展工作,读不相干的代码,似乎没用
我常常把写代码和写作进行类比 —— 二者有很多相通之处;但从培养写代码和写作的过程来看,二者又有很多不同。我们的写作能力,是建立在大量基础阅读的基础上的,是除了学习语法和文法知识外,从小学开始,经年累月,通过阅读各种不同层次的名家的作品,再加上各种各样的写作训练,累积出来的;而我们的写代码的能力,在了解和掌握了语法/文法之后(学习和抄写 example 代码也算语法/文法学习的一部分),跳过了大量阅读名家作品的过程,直接 biu 地一下就自动养成了:学会基础的语法和试验了若干 example 后,我们就火箭般蹿到了自己写代码打怪赞经验的阶段。这样略过大量阅读代码的阶段有三个害处:
写代码的基础是不牢靠的,打怪升级的过程也是最慢的。道理很简单 —— 前辈们踩过的坑,总结的经验教训,你都不得不亲自用最慢的法子一点点试着踩一遍。
很容易养成 stackoverflow driven 的写代码习惯 —— 遇到不知如何写的代码,从网上找现成的答案,找个高票的复制粘贴改吧改吧,凑活着完成功能再说。写代码的过程中遇到问题,开启调试模式,要么设置无数断点一步步跟踪,要么到处打印信息试图为满是窟窿的代码打上补丁,导致整个写代码的过程是一部调代码的血泪史。(见我的文章:你要避免的软件开发模式)
你周围最强的那个工程师的开发水平的上限就是你的上限。
下图是作为程序员最需要了解的源码体系:
四、工具的使用
工欲善其事必先利其器,工具对Java程序员的重要性不言而喻现在有很多库、实用工具和程序任Java开发人员选择。下图列出的工具都是程序员必不可少的工具
五、性能优化
性能优化,简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。性能问题永远是永恒的主题之一,而优化则更需要技巧。
网友评论