11个简单的Java性能调优技巧
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
11个简单的Java性能调优技巧
⼤部分建议是针对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=new StringBuilder(“This is a test”);
for (int i=0; i<10; i++) {
sb.append(i);
sb.append(” “);
}
log(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, stName ”
+ “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 this
log.debug(“User [” + userName + “] called method X with [” + i + “]”);
// or this
log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));
在上⾯两种情况中,你都将执⾏创建⽇志消息所有必需的步骤,在不知道⽇志框架是否将使⽤⽇志消息的前提下。
因此在创建调试消息之前,最好先检查当前的⽇志级别。
// do this
if (log.isDebugEnabled()) {
log.debug(“User [” + userName + “] called method X with [” + 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 this
test.replace(“test”, “simple test”);
// with this
StringUtils.replace(test, “test”, “simple test”);
11.缓存昂贵的资源,如数据库连接
缓存是避免重复执⾏昂贵或常⽤代码⽚段的流⾏解决⽅案。
总的思路很简单:重复使⽤这些资源⽐反复创建新的资源要便宜。
⼀个典型的例⼦是缓存池中的数据库连接。
新连接的创建需要时间,如果你重⽤现有连接,则可以避免这种情况。
你还可以在Java语⾔本⾝找到其他例⼦。
例如,Integer类的valueOf⽅法缓存了-128到127之间的值。
你可能会说创建⼀个新的Integer 并不是太昂贵,但是由于它经常被使⽤,以⾄于缓存最常⽤的值也可以提供性能优势。
但是,当你考虑缓存时,请记住缓存实现也会产⽣开销。
你需要花费额外的内存来存储可重⽤资源,因此你可能需要管理缓存以使资源可访问,以及删除过时的资源。