
Java应用的性能瓶颈,往往不在于语言本身,而在于编码实践。养成良好的习惯,能显著提升程序效率。
1. 在合适的场景使用单例模式
单例模式通过减少对象创建来节约资源、提升加载效率。它主要适用于三种场景:
- 控制资源的并发访问。
- 控制实例数量,节约资源。
- 在不建立直接关联的模块间实现数据共享。
2. 谨慎使用静态变量
当一个对象被静态变量引用时,只要其所属的类未被卸载,该对象便常驻内存,难以被垃圾回收(GC)。这会增加内存占用,直到程序终止。更多关于Java内存管理的知识,有助于理解此处的原理。
public class A {
private static B b = new B(); // b的生命周期与A类同步
}
3. 避免过度创建对象
在循环或高频调用的方法中频繁new对象,会大量消耗创建与垃圾回收的时间。应在可控范围内最大化重用对象,或考虑使用基本类型、数组替代对象。
4. 善用final修饰符
final修饰的类不可继承,其所有方法隐含为final。编译器会尝试内联(inline)final方法,这平均能带来约50%的性能提升。例如,将简单的getter/setter方法设为final。
原始示例:
class MAF {
public void setSize (int size) {
_size = size;
}
private int _size;
}
优化后:
class DAF_fixed {
final public void setSize (int size) {
_size = size;
}
private int _size;
}
5. 优先使用局部变量
方法参数和局部变量存储在栈(Stack)中,访问速度快。而实例变量、静态变量等在堆(Heap)中分配,访问相对较慢。
6. 区分基本类型与包装类型
基本类型在栈上处理,效率高;包装类型是堆中的对象。在集合类等需要对象的场景使用包装类型,其他计算场景提倡使用基本类型。
7. 减小同步范围
同步synchronized会带来较大开销,甚至可能导致死锁。应尽量减少同步代码块的范围,并优先考虑使用方法同步而非代码块同步,以降低锁的粒度。
8. 避免使用finalize方法进行资源清理
依赖finalize()方法清理资源会增加GC负担,严重影响程序性能。资源的释放应通过显式调用close()等方法或在finally块中完成。
9. 字符串创建的优化
String str = “hello”; // 推荐,利用JVM字符串常量池
String str = new String(“hello”); // 不推荐,额外创建char[]对象
第二种方式会额外生成一个底层的char[]数组。
10. 非线程安全场景使用高效集合
在多线程安全无虞的情况下,应优先使用HashMap、ArrayList,而非其线程安全但性能较低的旧版替代品Hashtable、Vector。
11. 合理初始化HashMap容量
创建HashMap时,应根据预估容量合理设置初始大小(initialCapacity)和负载因子(loadFactor),避免多次扩容(rehash)。
public HashMap(int initialCapacity, float loadFactor); // 充分利用此构造函数
12. 减少循环内的重复计算
将循环中不变的条件计算提取到循环外。
// 不推荐
for (int i = 0; i < list.size(); i++)
// 推荐
for (int i = 0, len = list.size(); i < len; i++)
13. 延迟对象创建
只在真正需要时才创建对象。
// 不推荐
A a = new A();
if (i == 1) {
list.add(a);
}
// 推荐
if (i == 1) {
A a = new A(); // 需要时再创建
list.add(a);
}
14. 在finally块中释放资源
确保资源(如I/O流、数据库连接)在任何情况下都能被正确关闭,避免资源泄漏。
// 示例
try {
// 获取和使用资源
} finally {
// 确保资源被关闭
}
15 & 16. 使用移位代替乘除
对于乘以或除以2的幂次方运算,使用移位操作效率更高。但需添加注释以增强可读性。
int num = a / 4; // -> a >> 2
int num = a * 8; // -> a << 3
17. 为StringBuffer指定初始容量
默认容量(16)不够时,StringBuffer会进行耗时的扩容和数据拷贝。预先估算大小可避免此开销。
18. 适时置null释放大对象引用
在方法中,局部引用变量通常随方法结束而失效。但若方法后半段有耗时或大内存操作,可显式将不再使用的对象引用置为null,以助GC尽早回收。
19. 避免使用二维数组
二维数组占用的内存空间远大于一维数组。
20. 慎用split方法
String.split()因支持正则而效率较低。高频调用时可考虑用StringUtils.split或缓存结果。在大数据处理场景中,此类优化尤为关键,更多技巧可参考大数据相关内容。
21. ArrayList与LinkedList的选择
- ArrayList:基于数组,随机访问快(O(1)),但中间插入/删除慢(需移动元素)。
- LinkedList:基于链表,插入/删除快(O(1)),但随机访问慢(需遍历)。
根据主要操作类型选择。
22. 使用System.arraycopy()复制数组
System.arraycopy()是原生方法,远比循环复制数组高效。
23. 缓存常用对象
对频繁使用的对象进行缓存,可使用容器或第三方缓存库(如EhCache、Oscache),但需注意防止缓存占用过多内存。
24. 避免超大内存分配
堆内存碎片化后,分配大块连续内存会变得困难,可能导致分配失败。
25. 异常创建的代价
创建异常需要生成完整的栈跟踪(Stack Trace),开销很大。应仅在异常情况下使用,避免在性能关键路径上频繁创建异常。
26. 重用对象,特别是String
字符串拼接应使用StringBuffer或StringBuilder,避免生成大量中间String对象。
27. 避免重复初始化变量
JVM会为成员变量设置默认值(0, null, false等)。在构造函数中调用方法进行初始化需谨慎,防止因对象未完全初始化而抛空指针。
// 不推荐
public int state = this.getState(); // getState()可能依赖未初始化的成员
// 推荐在init()方法中赋值
28. SQL语句使用大写(Java+Oracle场景)
在Oracle数据库中,将内嵌的SQL关键字写为大写,可减轻其解析器的负担。
29. 及时关闭资源
数据库连接、I/O流等使用后必须立即关闭,释放系统资源。
30. 手动辅助GC
对于大对象或使用完毕的对象,可将其引用显式设为null,向GC提示回收时机,尤其是在可能发生内存泄漏的场景。
31. 同步机制的选择
与方法同步相比,精确控制的代码块同步通常更好。但在简单场景下,使用方法同步可使代码更简洁。
32. Try/Catch置于循环外
将try-catch放在循环外部,避免在每次迭代中都进行异常处理结构的构造。
33. 为StringBuffer预设容量
默认容量16往往不够,提前设置合理的初始容量可避免多次扩容和数据拷贝。
34. 合理使用Vector
- 添加元素:默认容量10,扩容时复制全部元素。
- 插入元素:
vector.add(index,obj)会导致后续元素后移。
- 删除元素:删除末尾元素
remove(size()-1)比删除头部元素开销小。
- 删除所有元素:使用
removeAllElements()。
- 按对象删除:
vector.remove(obj)比先查索引再删除高效。
35. 考虑使用clone()替代new
对于实现了Cloneable接口的对象,使用clone()方法创建实例不会调用构造函数,在某些场景下比new更快(如原型模式)。
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone(); // 不调用构造函数
}
36. 高效遍历HashMap
使用EntrySet进行遍历,直接获取键和值,效率高于先取keySet再get(key)。
Map<String, String[]> paraMap = new HashMap<>();
for (Entry<String, String[]> entry : paraMap.entrySet()) {
String key = entry.getKey();
String[] value = entry.getValue();
}
37. 数组(Array) vs ArrayList
- Array:定长,效率最高。
- ArrayList:动态扩容,使用灵活但有效能损耗。
38. 单线程首选HashMap、ArrayList
在无并发需求的场景,使用非同步的HashMap、ArrayList而非Hashtable、Vector。
39. StringBuffer与StringBuilder的选择
- StringBuffer:线程安全,性能稍低。
- StringBuilder:非线程安全,性能提升约10%-15%。
在非并发场景下,可使用
StringBuilder;若涉及线程安全,StringBuffer是更稳妥的选择。
40. 多用静态方法
无需访问对象成员(非static)的方法,可声明为static。静态方法调用更快,且能明确表明该方法不依赖或改变对象状态。
性能优化是时间、空间与代码可维护性间的权衡。以上技巧需根据实际业务场景灵活运用,方能达到最佳效果。