欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

进阶 Java 高手必须知道的 11 个优化建议

程序员文章站 2022-03-23 09:27:06
这篇文章就是告诉你,如何通过消除瓶颈、缓存和一些细节调整来优化性能。大多数开发人员认为性能优化是一个复杂的话题,需要很多的经验积累和知识储备才能搞定,这话不全对。优化性能确实不是一个容易的事情,但并不意味你不了解全貌就不能优化,就像不积跬步无以至千里,下面是我积累的一些性能优化的建议,可以帮助你从现在开始。这些建议大多数针对于 Java 语言,但是原理却有一些通用性,好了,我们直接从几个通用的性能优化建议开始。1. 做必要的优化一定要记住一个最重要的优化原则,只有发现必须做的优化,否则不....

这篇文章就是告诉你,如何通过消除瓶颈、缓存和一些细节调整来优化性能。

大多数开发人员认为性能优化是一个复杂的话题,需要很多的经验积累和知识储备才能搞定,这话不全对。优化性能确实不是一个容易的事情,但并不意味你不了解全貌就不能优化,就像不积跬步无以至千里,下面是我积累的一些性能优化的建议,可以帮助你从现在开始。

这些建议大多数针对于 Java 语言,但是原理却有一些通用性,好了,我们直接从几个通用的性能优化建议开始。

1. 做必要的优化

一定要记住一个最重要的优化原则,只有发现必须做的优化,否则不要做任何标准库的替换或者进行复杂的逻辑优化。

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

那么关键问题就来了,如何评估是必要的优化呢?

首先你需要定义标准,比如你程序的 API 的响应时间是多少或者单位时间内处理的请求书,这个确定以后你就可以查看哪些地方慢,哪些地方需要改进,接下来需要做的事情就是看第二个技巧。

2. 查找真正的瓶颈

在遵循了第一个建议并确定了需要改进的部分以后,从哪里开始呢?

可以通过如下两种方法解决这个问题:

  • 从可疑或者可能造成问题的地方开始。
  • 或者通过分析器找到出现瓶颈的地方。

通过分析器找到问题的办法可以让你更好的理解代码的性能,同时可以专注于最关键的部分,当然如果对于性能的分析你从未试过使用分析器,那么仅仅靠猜测的话估计只会凭借自己的直觉南辕北辙。

3. 性能测试

这个一个非常重要的方式,提前写好程序的性能测试,这样就可以在你性能优化前后运行性能测试,这样就可以实际的评估出部署程序以后实际解决性能问题的情况。同时可以避免一些因为性能优化导致的程序问题,比如你想针对数据库做缓存,那么这点就显得尤为重要了。

4. 优先处理最大的瓶颈

在创建性能测试程序和使用 Profiler 分析应用程序以后,你会发现有一堆问题要去修复,但是问题又来了,还是不知道从何入手。

从一个可以最快解决问题改善性能的地方开始,这也是可以让你说服团队进行性能评估的必要性最好的依据了。但是相反,我建议从最重要的性能问题开始,这也是可以最大限度的改进性能。到这里通用的性能优化思路差不多了,我们了解一些 Java 语言特定的优化方案。

5. 使用 StringBuilder 连接字符串

在 Java 中有许多不同的选项来连接字符串。例如,+或+=, StringBuffer 或 StringBuilder。那么你应该选择哪种呢?

其实这依赖你具体的场景,如果你是 for 循环里面做字符串的拼接,那么推荐使用 StringBuilder,它比 StringBuffer 有更好的性能,当然需要分析当前的场景是否需要线程安全,否则不得不使用 StringBuffer。

可以非常简单的创建 StringBuilder 实例,然后通过 append 追加字符串,最后通过 toString 方法转换为字符串。下面就是一个最简单的例子,最终输出 This is a test0 1 2 3 4 5 6 7 8 9。

StringBuilder sb = new StringBuilder(“This is a test”);
for (int i=0; i<10; i++) {
    sb.append(i);
    sb.append(" ");
}
log.info(sb.toString());

StringBuilder 构造函数会默认初始化当前字符串的长度加16字符长度长度,当你添加更多的字符的时候,JVM 会动态增加 StringBuilder 的 size,所以如果你已经确切知道了当前 StringBuilder 需要容纳多少字符,那么可以直接初始化的时候固定下来 size,这样可以优化性能。

6. 在一个语句中拼接字符串使用 +

等下这个问题不是和 5 冲突了吗?其实不是的。因为 String 的不可变性,如果在 for 循环中拼接 String 每次都会创建一个新对象,但是一个语句中的 + 则不然, JVM 会在编译的时候针对一个字符串的 + 拼接做性能优化,最终使用一个 String 对象,如下面的例子。

Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);

7. 尽可能使用基本类型而不是包装类型

一个很常见并且很普遍的做法就是使用基本类型而不是包装类型,比如使用 int 替代 Integer,使用 double 替代 Double,这样可以使 JVM 把变量存储在 Stack 里面而不是 Heap 里面来减少整体的内存消耗。

8. 尽量避免使用BigInteger和BigDecimal

上面我们已经在讨论数据类型,那我们继续看一下BigInteger和BigDecimal,尤其是BigDecimal在精度上面给我们带来的利好足以让我们爱不释手,然而代价却是昂贵的。

与简单的long或double相比,BigInteger和BigDecimal需要更多的内存,从而大大降低了所有计算的速度。所以需要再三考虑是否存储值超过了 long 类型的最大范围或者是精度无法控制了,如果不是优先考虑基本类型。

9. 首先检查当前日志级别

这个对性能的影响还是非常明显的,然而你会发现大部分的代码中都忽略了这个问题。正确的姿势是先判断级别,不然你会创建一个 String 对象然后却用不到他。下面就是两个例子。

log.debug(“User [” + userName + “] called method X with [” + i + “]”);
log.debug(String.format(“User [%s] called method X with [%d]”, userN

所以正确的做法应该是如下

if (log.isDebugEnabled()) {
    log.debug(“User [” + userName + “] called method X with [” + i + “]”);
}

10. 使用 Apache Commons StringUtils.replace 代替 String.replace

通常情况下 StringUtils.replace 的性能会比 String.replace 性能好,所以推荐使用前者,关于这个性能比较有一篇文章有详细的说明:《String.replace 用的不对性能可能差 10 倍,你用对了吗?》

test.replace(“test”, “simple test”);
StringUtils.replace(test, “test”, “simple test”);

11. 对一些昂贵的资源进行缓存

缓存是一种流行的解决方案,可以避免重复执行昂贵或经常使用的代码片段。总体思路很简单:重复使用这些资源要比一次又一次地创建新资源要节省资源。

一个典型的示例是在池中缓存数据库连接。创建新连接需要花费时间,当然池这个技术也是非常普遍的,比如数据库连接池、线程池等等。

本身 Java 中 Integer类的valueOf方法缓存-128到127之间的值也是这个道理,虽然创建新的Integer成本本身很高,但是如果它的使用频率很高性能的开销就会显得非常明显。

当然也需要考虑缓存带来的开销,毕竟你缓存了内容就需要在资源过期的时候进行更新。

总结

所以从上面的建议中你可以发现,我们仅仅用了很简单的方式,很少的操作就可以针对性能做了很大的帮助,所以呢:

  • 针对真正的必要做优化
  • 使用工具找到性能瓶颈
  • 先从最大的性能问题入手

推荐阅读:

牛皮了,马士兵老师全网首播阿里P8级技术、实现大型淘宝实战落

面试美团被JVM惨虐?阿里P9架构师用500分钟把JVM从入门讲到实战#合集

清华启蒙架构师马士兵针对应届生到开发十年的Java程序员做职业把脉

马士兵教育:Spring源码实战全集,资深架构师带你搞懂Spring源码底层从入门到入坟

阿里P9架构师120分钟带你掌握线程池,不在为线程而烦恼

 

本文地址:https://blog.csdn.net/weixin_45132238/article/details/107382312

相关标签: 架构 java