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

互联网全栈架构

共 1502字,需浏览 4分钟

 · 2020-11-01

点击上方 果汁简历 ,选择“置顶公众号”

优质文章,第一时间送达

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

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

这些建议大多数针对于 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成本本身很高,但是如果它的使用频率很高性能的开销就会显得非常明显。

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

总结

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

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

浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报