负载测试和应用程序监控对于确定应用程序的一些关键性能瓶颈非常有用。但同时,我们需要遵循良好的编码习惯,以避免在对应用程序进行监控的时候出现过多的性能问题。
在下一章节中,我们将来看一些最佳实践。
字符串连接是一个非常常见的操作,也是一个低效率的操作。简单地说,使用+=来追加字符串的问题在于每次操作都会分配新的String。
下面这个例子是一个简化了的但却很典型的循环。前面使用了原始的连接方式,后面使用了构建器:
public String stringAppendLoop() {
String s = ""; for (int i = 0; i < 10000; i++) { if (s.length() > 0)
s += ", ";
s += "bar";
} return s;
}public String stringAppendBuilderLoop() {
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { if (sb.length() > 0)
sb.append(", ");
sb.append("bar");
} return sb.toString();
}
上面代码中使用的StringBuilder对性能的提升非常有效。请注意,现代的JVM会在编译或者运行时对字符串操作进行优化。
导致出现StackOverFlowError错误的递归代码逻辑是Java应用程序中另一种常见的问题。如果无法去掉递归逻辑,那么尾递归作为替代方案将会更好。
我们来看一个头递归的例子:
public int factorial(int n) { if (n == 0) { return 1;
} else { return n * factorial(n - 1);
}
}
现在我们把它重写为尾递归:
private int factorial(int n, int accum) { if (n == 0) { return accum;
} else { return factorial(n - 1, accum * n);
}
}public int factorial(int n) { return factorial(n, 1);
}
其他JVM语言(如Scala)已经在编译器级支持尾递归代码的优化,当然,对于这种优化目前也存在着一些争议。
正则表达式在很多场景中都非常有用,但它们往往具有非常高的性能成本。了解各种使用正则表达式的JDK字符串方法很重要,例如String.replaceAll()、String.split()。
如果你不得不在计算密集的代码段中使用正则表达式,那么需要缓存Pattern的引用而避免重复编译:
static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");
使用一些流行的库,比如Apache Commons Lang也是一个很好的选择,特别是在字符串的操作方面。
线程的创建和处置是JVM出现性能问题的常见原因,因为线程对象的创建和销毁相对较重。
如果应用程序使用了大量的线程,那么使用线程池会更加有用,因为线程池允许这些昂贵的对象被重用。
为此,Java的ExecutorService是线程池的基础,它提供了一个高级API来定义线程池的语义并与之进行交互。
Java 7中的Fork/Join框架也值得提一下,因为它提供了一些工具来尝试使用所有可用的处理器核心以帮助加速并行处理。为了提高并行执行效率,框架使用了一个名为ForkJoinPool的线程池来管理工作线程。