让你月薪飙升的秘籍:Java性能调优的9个实用技巧

  现实里可能没有完美无缺的代码。如果有,那么,过来,我写一段代码给你看。

  Java已经成为了编程语言的骄子。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,越来越多的企业在数据结构、算法分析、软件开发等研究设计时,都选择以Java语言作为载体。这说明Java语言已经是人们构建软件系统时主要使用的一种语言。如何让Java程序运行是一回事,而如何让它们跑的快又是另外一回事了……

讓你月薪飆升的祕籍:Java效能調優的9個實用技巧

  下面我整理了一些Java性能调优的一些技巧,在此和大家浅浅的交流一下。

  Java性能优化的重要性:

  代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。

  代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。

  代码优化的目标是:

  减小代码的体积

  提高代码运行的效率

  在我们分享基于Java的性能调优技巧之前,让我们先讨论一下这些通用的性能调优技巧。

  通用性能调优的4个实用技巧

  1. 在必要之前,先不要优化

  这可能是最最重要的性能调优技巧之一。你应该遵循常见的最佳实践,并尝试有效地实现你的用例。但这并不意味着在证明它是必要之前,替换任何标准库或构建复杂的优化。

  在大多数情况下,过早的优化占用了大量的时间,使得代码难以读取和维护。更糟糕的是,这些优化通常不会带来任何好处,因为你花费了大量时间来优化应用程序的非关键部分。

  那么,你如何证明你需要优化某些东西呢?

  首先,你需要确定应用程序代码的速度,例如,为所有API调用指定一个最大响应时间,或者指定在特定时间范围内导入的记录数量。完成之后,你可以度量应用程序的哪些部分太慢而需要改进。当这样做之后,那么请继续看第二个调优技巧。

  2. 使用分析器来找到真正的瓶颈

  在你遵循第一条建议,并确定你的应用程序的某些部分的确需要改进之后,问自己从哪里开始?

  你可以用两种方法来解决这个问题:

  你可以看一下你的代码,从看起来可疑或者你觉得它可能会产生问题的部分开始。

  或者使用分析器,获取代码中每个部分的行为和性能的详细信息。

  至于为什么应该总是遵循第二种方法。

  答案应该很明显,基于分析器的方法能让你更好地理解代码的性能含义,并允许你关注最关键的部分。如果你曾经使用过分析器,你将会惊讶于代码的哪些部分造成了性能问题。然而,很多时候,你的第一次猜想会把你引向错误的方向。

  3. 为整个应用程序创建性能测试套件

  这是另一个帮助你避免许多意想不到问题的一般技巧,这些问题通常发生在性能改进部署到生产环境之后。你应该经常定义测试整个应用程序的性能测试套件,并在你完成性能改进之前和之后运行它。

  这些额外的测试运行将帮助你识别更改的功能和性能方面的影响,并确保你不会发布一个弊大于利的更新。如果你的任务运行于应用程序的多个不同部分比如数据库或缓存,这一点尤其重要。

  4. 首先解决最大的瓶颈问题

  在创建了测试套件并使用分析器对应用程序进行分析之后,你就有了一个需要提高性能的问题列表,这很好,但它仍然不能回答你应该从哪里开始的问题。你可以从那些可以快速搞定的开始,亦或者从最重要的问题开始。

  当然前者很诱人,因为这很快就能出结果。有时,可能需要说服其他团队成员或你的管理层,性能分析是值得的。

  但总的来说,我建议首先着手处理最重要的性能问题。这将为你提供最大的性能改进,而且你可能只需要修复这些问题中的几个就可以解决你的性能需求。

  在了解通用性能调优技巧之后,让我们再来仔细看看一些特定于Java的调优技巧。

  Java性能调优的5个技巧

  1. 使用 StringBuilder

  几乎所有Java代码中你都应该考虑这个问题。避免使用+号。你可能会认为StringBuilder只是个语法糖,比如:

  String x = “a” + args.length + “b”;

  会编译成

讓你月薪飆升的祕籍:Java效能調優的9個實用技巧

  但是之后你需要根据条件来修改字符串,会发生什么事情呢?

讓你月薪飆升的祕籍:Java效能調優的9個實用技巧

  你现在会有第二个StringBuilder,这个 StringBuilder 本来没有存在的必要,它会消耗堆内存,给 GC 增加负担。你应该这样写:

讓你月薪飆升的祕籍:Java效能調優的9個實用技巧

  2. 避免正则表达式

  正则表达式相对便宜和方便。但是如果你在N.O.P.E 分支,那很糟糕了。如果你必须在计算机密集的代码段中使用正则表达式,至少把Pattern的引用缓存下来,避免每次都对其重新编译:

  static final Pattern HEAVY_REGEX =

  Pattern.compile(“(((X)*Y)*Z)*”);

  但是如果你的正则表达式真的很简单,就像

  String[] parts = ipAddress.split(“\\.”);

  然后你真的最好诉诸普通的 char[] 或基于索引的操作。例如下面一段代码做了同样的事情:

讓你月薪飆升的祕籍:Java效能調優的9個實用技巧

  这也说明了为什么你不应该过早进行优化。与 split() 的版本相比,这简直不可维护。

  正则表达式很有用,但需要代价。如果你在N.O.P.E 分支,就必须避免正则表达式的代价。

  3. 不要使用 iterator()

  这个建议不太适用于常规用例,只适用于 N.O.P.E. 分支,但你也可以用用看。编写 Java-5 风格的 foreach 循环很方便。 你可以完全忽略循环内部变量,并编写:

  for(Stringvalue:strings){

  //Dosomethingusefulhere}

  然而,每当你运行到循环内部时,如果 string 是一个Iterable,你就要创建一个新的Iterator实例。如果你正在使用ArrayList,这将会在堆上分配一个含 3 个 int 的对象:

  privateclassItrimplementsIterator<E>{

  intcursor;

  intlastRet=-1;

  intexpectedModCount=modCount;

  //…

  相反,你可以编写以下代码——等价循环体,并且在栈上仅“浪费”一个 int 值,开销低:

  intsize=strings.size();for(inti=0;i<size;i++){

  Stringvalue:strings.get(i);

  … 或者,你可以选择不改变链表,在数组版本上使用同样的操作:

  for(Stringvalue:stringArray){

  //Dosomethingusefulhere}

  关键点

  从可写性和可读性以及从 API 设计的角度来看,Iterators、Iterable 和 foreach 循环都是非常有用的。但它们在堆上为每次单独的迭代创建一个小的新实例。 如果你运行这个迭代许多次,又想避免创建这个无用的实例,可以使用基于索引的迭代。

  4. 不要调用这些方法

  一些方法简单但开销不小。在N.O.P.E.分支示例中,我们没有在叶节点上使用这样的方法,但你可能使用到了。我们假设 JDBC 驱动程序需要耗费大量资源来计算ResultSet.wasNull()的值。你可能会用下列代码开发 SQL 框架:

  if(type==Integer.class){

  result=(T)wasNull(rs,

  Integer.valueOf(rs.getInt(index)));

  }

  //Andthen…staticfinal<T>TwasNull(ResultSetrs,Tvalue)throwsSQLException{

  returnrs.wasNull()?null:value;

  }

  此处逻辑每次都会在你从结果集中获得一个 int 之后立即调用 ResultSet.wasNull()。但getInt() 的约定是:

  返回: 列的数目;如果这个值是 SQL NULL,这个值将返回 0。

  因此,对上述问题的简单但可能有效的改进将是:

  staticfinal<TextendsNumber>TwasNull(

  ResultSetrs,Tvalue

  )throwsSQLException{

  return(value==null||

  (value.intValue()==0&&rs.wasNull()))

  ?null:value;

  }

  因此,这不需要过多考虑。

  关键点

  不要在算法的“叶节点”中调用开销昂贵的方法,而是缓存该调用,或者如果方法规约允许则规避之。

  5. 使用基本类型和栈

  上面的例子大量使用了泛型。泛型会强制对 byte、short、int 和 long 这些类型进行装箱 —— 至少在这之前:泛型会在 Java 10 和 Valhalla 项目中实现专业化。不过现在你的代码里并没实现这种约束,所以你得采取措施:

  //GoestotheheapIntegeri=817598;

  … 替换为下面这个:

  //Staysonthestackinti=817598;

  如果你使用数组的话,情况不太妙:

  //Threeheapobjects!Integer[]i={1337,424242};

  … 替换成这个:

  //Oneheapobject.int[]i={1337,424242};

  关键点

  当你在深入N.O.P.E. 分支时,要小心使用装箱类型。你可能会给 GC 制造很大的压力,因为它必须一直清理你的烂摊子。

  有一个特别有效的办法对此进行优化,即使用某些基本类型,并为它创建一个巨大的一维数组,以及相应的定位变量来明确指出编码后的对象放在数组的哪个位置。

  LGPL 授权的trove4j库实现了基本数据类型的集合,它看起来比 int[] 要好些。

  总结:

  正如你所看到的,提高应用程序的性能有时不需要做大量的工作。这篇文章中的大多数建议,其实只需要稍微的努力就可以将它们应用到代码中。

  但通常最重要的建议是很编程语言无关的:

  在你知道有必要之前,不要优化

  使用分析器来找到真正的瓶颈

  首先解决最大的瓶颈问题

Sharing is caring!

未经允许不得转载:壹头条 » 让你月薪飙升的秘籍:Java性能调优的9个实用技巧

赞 (0)