90 % Java 程序员被误导的一个性能优化策略
我们经常看到一些 Java 性能优化的书或者理念,说不要在循环内定义变量,这样会占用过多的内存影响性能,而要在循环外面定义。接触 Java 这么久以来,相信很多 Java 程序员都被这种代码性能优化策略所误导。
看下面两个示例,示例1在循环外定义变量,示例2是在循环内定义变量。
/**
* 循环外定义变量
*/
private static void outer() {
Javastack javastack = null;
for (int i = 0; i < 10; i++) {
javastack = new Javastack();
}
}
/**
* 循环内定义变量
*/
private static void inner() {
for (int i = 0; i < 10; i++) {
Javastack javastack = new Javastack();
}
}
先来分析这两个示例吧。
循环外定义变量
循环外定义变量,变量循环内每次引用指向不同的对象实例,每次循环变更对象实例时,上一次被指向的对象就会被销毁,直到最后一个循环。这样,循环结束后,这个变量还存在,并指向循环内最后一个对象实例,其他对象都销毁了。
这样,本应该是循环体内的生命周期变量被扩散到了循环外,如果循环外依旧用这个变量,会导致后面的业务发生不可预知的后果。这种问题在笔者工作当中经常会遇到,看下面的例子。
/**
* 循环外定义变量
*/
private static void outer() {
Javastack javastack1 = null;
for (int i = 0; i < 10; i++) {
javastack1 = new Javastack();
}
Javastack javastack2 = userDao.getUser(10);
}
上面定义了一个 javastack2
,如果此时在后续代码或者传递到别的方法时写错了,用了 javastack1
,那这时不就有问题了吗?这只是一方面,还有如果用同一变量名,当这一变量被重用时发生异常,本来发生异常应该是 null 值的,结果得到了是之前循环体内的值。
循环内定义变量
循环内定义变量,和循环外略有不同的是,每次都会创建新的局部变量指向新的对象实例,每个变量和对象的生命周期仅限于在循环体之内,而且每次循环结束该局部变量和对象实例都会随着循环体的结束而销毁,所以不存在占用更多的内存这一说法。
总结
两种用法都会创建相同数量的对象实例,只不过循环内会反复创建相同数量的局部变量,栈内存垃圾回收频率也会更高,但对于堆垃圾回收带来的性能影响和变量生命周期带来的业务影响来说,栈内存这点性能影响可以忽略不计。
所以,建议使用循环内定义变量,这种把变量的生命周期限制在循环体范围内,也不会出现业务上重用变量而导致严重的问题。