一 java性能优化的50个细节
1 尽量再合适的场合使用单例
使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但是并不是所有的地方都适合使用单例。单例主要使用于以下三个方面
1. 控制资源的使用,通过线程同步来控制资源的并发访问
2. 控制实例的产生,节约资源
3. 控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程实现通信
2 尽量避免随意使用静态变量
当某个对象被定义为static变量的引用,通常GC不会回收这个对象所占有的内存
3 尽量避免过多过常的创建java对象
尽量避免在经常调用的方法,循环中new对象,由于系统不仅需要花费时间来创建对象,而且还要花费时间来对这些对象进行垃圾回收。在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象
4 尽量使用final修饰符
带有final修饰符的类是不可以派生的
如果一个类是final,该类的所有方法都是final。java编译器会寻找机会内联所有的final方法,这和具体编译器实现有关,此举能让性能平均提高百分之五十
如让setter/getter方法为final
5 尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快。其他变量,如静态变量,实例变量都创建在堆中,速度较慢
6 尽量处理好包装类型和基本类型两者的使用场所
虽然包装类型和基本类型在使用过程中可以相互转换,但是他们两者所产生的内存区域时完全不同的,基本类型数据产生和处理都在栈中处理,包装类型时对象,在堆中产生实例
在集合类对象,有对象方面需要的处理适合包装类型,其他的处理提倡使用基本类型
7 慎用synchronized,尽量减少sychronize方法
实现同步时需要很大的系统开销为代价,甚至可能造成死锁
sychroinze方法尽量减小,并且应该尽量使用方法同步代替代码块同步
8 尽量不要使用finalize方法
将资源清理放在finalize方法中完成是非常不好的选择,由于GC工作量很大,尤其是回收Young代内存时,大都会引起应用程序暂停,所以在选择使用fianlize方法进行资源清理,会导致GC负担更大,程序效率根插
9 尽量使用基本数据类型代替对象
10 多线程在未发生线程安全的前提下应尽量使用HashMap,ArrayList
11 尽量合理的创建HashMap
避免HashMap多次进行重构,扩容是一件非常耗费性能的事
默认大小为16,负载因子为0.75
12 尽量减少对变量的重复计算
13 尽量避免不必要的创建
14 尽量在finally中释放资源
15 尽量使用移位来代替a/b操作
使用移位时应添加注释,移位操作不直观,不好理解
16 尽量使用移位来代替a*b操作
17 尽量避免过多过常的创建java对象
StringBuffer 的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能
18 尽量早释放无用对象的引用
大部分时,方法局部变量引用的对象会随着方法的结束而变成垃圾
19 尽量避免使用二维数组
20 尽量避免使用split
除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果
21 ArrayList与LinkedList
一个线性表,一个链表,对症下药
22 尽量使用System.arraycopy()代替通过循环来复制数组
快
23 尽量缓存经常用的对象
24 尽量避免非常大的内存分配
25 慎用异常
当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个 Exception 时,JVM 不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。
如果您创建一个 Exception,就得付出代价,好在捕获异常开销不大,因此可以使用 try-catch 将核心内容包起来。从技术上讲,你甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是throw操作——尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常,幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则
26 尽量重用对象
27 不要重复初始化变量
默认情况下,调用类的构造函数时,java会把变量初始化成确定的值,所有的对象被设置成null,整数变量设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键字创建一个对象时,构造函数链中的所有构造函数都会被自动调用。
这里有个注意,给成员变量设置初始值但需要调用其他方法的时候,最好放在一个方法。比如initXXX()中,因为直接调用某方法赋值可能会因为类尚未初始化而抛空指针异常
28 在java+Oracle的应用系统开发中,java中内嵌的SQL语言应尽量使用大写形式,以减少Oracle解析器的解析负担
29 在java编程过程中,进行数据库连接,I/O流操作,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销
30 过分的创建对象会消耗系统的大量内存,严重时,会导致内存泄漏,因此,保证过期的对象的及时回收具有重要意义。JVM的GC并非十分智能,因此建议在对象使用完毕后,手动设置成null
31 在使用同步机制时,应尽量使用方法同步代替代码块同步
32 不要在循环中使用Try/Catch语句,应把Try/Catch放在循环最外层
Error是获取系统错误的类,或者说是虚拟机错误的类。不是所有的错误Exception都能获取到的,虚拟机报错Exception就获取不到,必须用Error获取
33 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
复制老的对象数组很耗时
34 合理使用java.util.Vector
35 不用new关键字创建对象的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数
36 不要将数组声明为:public static final
37 HaspMap的遍历
遍历entryset
38 array(数组)和ArrayList的使用
array 数组效率最高,但容量固定,无法动态改变,ArrayList容量可以动态增长,但牺牲了效率
39 尽量使用基本数据类型代替对象
40 使用具体类比使用接口效率高,但结构弹性降低了,但现代IDE都可以解决这个问题
41 考虑使用静态方法,如果你没有必要去访问对象的外部,那么就使你的方法成为静态方法。它会被更快地调用,因为它不需要一个虚拟函数导向表。这同时也是一个很好的实践,因为它告诉你如何区分方法的性质,调用这个方法不会改变对象的状态
42 应尽可能避免使用内在的GET,SET方法
43 避免枚举,浮点数的使用
44 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
45 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
46 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
47 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
48 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
49 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
50 通过StringBuffer的构造函数来设定它的初始化容量,可以明显提升性能
二 八大能力
1 源码剖和和框架定制能力
2 平台性能分析和调优能力
3 分布式架构设计能力
4 海量数据存储能力
5 高并发处理能力
6 解决方案和中间件实战能力
7 容器技术应用和集群化部署能力
8 海量数据搜索和实时计算能力
三 HashMap七种遍历方式与性能分析
3.1 HashMap遍历
3.1.1 从遍历大的方向来说,可以分为四类
1. 迭代器Iterator方式遍历
2. For Each方式遍历
3. Lambda表达式遍历
4. Stream API遍历
3.1.2 每种类型又有不同的实现方式,具体遍历方式可分为七种
1. 使用迭代器EntrySet方式遍历
2. 使用迭代器KeySet方式遍历
3. 使用For Each EntrySet方式遍历
4. 使用For Each KeySet方式遍历
5. 使用Lambda表达式遍历
6. 使用Streams API单线程遍历
7. 使用Streams AP多线程遍历
3.2 性能测试
使用oracle官方提供的性能测试工具JMH(Java Microbenchmark Harness,java微基准测试套件)来测试七种遍历的性能
结论:entrySet的性能比keySet的性能高出一倍之多,因此,尽量使用entrySet来实现map集合的遍历
3.3 字节码分析
使用迭代器或者for循环entrySet时,他们的性能都是相同的,因为他们最终生成的字节码基本上都是一样的
keySet的两种遍历方式也类似
3.4 性能分析
EntrySet之所以比KeySet性能高是因为,KeySet在循环时使用了map.get(key),而map.get(key)相当于又遍历了以便Map集合去查询key所对应的值。在使用迭代器或者for循环时,已经便利了一遍Map集合,再使用map.get(key)查询时相当于遍历了两边
EntrySet只遍历了以便Map集合,直接从Entry对象中取值
EntrySet性能比KeySet性能高出一倍,因为KeySet相当于便利了两边Map集合,而EntrySet只循环了一遍
3.5 安全性测试
迭代器中循环删除数据安全
for循环中删除数据非安全
Lambda循环中删除数据非安全
Stream循环中删除数据非安全
3.6 总结
不能再遍历集合中使用map.remove删除数据,这是非安全的操作方式,但是可以使用迭代器的iterator.remove方法来删除数据,这样时安全的
可以使用Lambda的removeIf来提前删除数据,或者是使用Stream中的filter过滤掉要删除的数据进行循环,这样都是安全的,再for循环前删除数据再遍历也是线程安全的
注:尽量使用迭代器来遍历EntrySet的方式来操作map集合,这样即高效也安全
四 switch与if
switch比if快
4.1 性能分析
从字节码可以看出,在switch中只取出了一次变量和条件进行比较,而if中每次都会取出变量和条件进行比较,因此switch比if快
4.2 提升测量量
分支的判断条件越多,switch性能高的特性体现的就越明显
4.3 switch的秘密
对于switch来说,最终生成的字节码有两种形态,一种是tableswitch,另一种时lookupswitch,决定最终生成的代码使用哪种状态取决于switch的判断条件是否紧凑。紧凑则时tableswitch否则为loopupswitch
4.4 tableswitch loopupswitch
当执行一次tableswitch时,堆栈顶部的int值直接用作表中的索引,以便抓取跳转目标并立即执行跳转。也就是说tableswitch的存储结构类似于数组,是直接用索引获取元素的,所以整个查询的时间复杂度为O(1),这也意味着他的搜索速度非常快
执行lookupswitch时,会诸葛进行分支比较或者使用二分法进行查询,查询复杂度为O(logN),所以使用tableswitch比lookupswitch快
五 代码中!=null判断优化
为避免空指针,代码中经常会做!=null判断
5.1 为null的两种情况
5.1.1 null是一个有效有意义的返回值
这种情况下null是一个看上去合理的值,比如查询数据时,在某个条件下并没有对应记录,此时null表达了空的概念
当返回类型为collection时,返回一个空的collection而不是null。如果返回值是一个普通对象,返回一个空的对象,而不是null
解决这个问题的一个方式就是Null Object Pattern空对象模式
5.1.2 null是一个无效的有误的返回值
null是一个不合理的参数,应该明确的中断程序,往外抛出错误。这种情况常见于api方法。例如,开发了一个接口,有一个必选参数,但是调用方并没有提供这个参数,因此,需要判断
相对于判空语句,更好的检查方式有两个
1. assert语句:可以把错误原因放到assert的参数中,这样不仅能够保护程序不往下走,而且还能把错误原因返回给调用方,一举两得
2. 可以直接抛出空指针异常。有问题要及时抛出
六 三目运算符与空指针
三目运算符中,表达式一和表达式2在设计算算数计算或者数据类型转换时,会触发自动拆箱。其中操作数为null时会导致空指针异常
当第二位和第三位操作数的类型相同时,三目运算符表达式的结果和这两位操作数的类型相同。
当第二位,第三位操作数分别为基本类型和该基本类型对应的包装型时,那么该表达式的结果的类型要求时基本类型
当第二位和第三位表达式都是包装类型的时候,该表达式的结果才是包装类型,否则只要有一个表达式为基本数据类型,结果都为基本数据类型。如果结果不符合预期,那么编译器会自动拆箱
注:如果涉及三目运算符,需要注意自动拆箱问题
七 优雅的处理空值
7.1 业务中的空值
7.1.1 如何约束入参
1. 强制约束,可以通过jsr 303进行严格的约束声明
2. 文档型约束(弱提示)
3. 总结:
- 空集合返回值:如果有能说服自己的理由,否则返回空集合而不是null
- Optional:如果是java8则引入它,否则使用Guava的Optional,或者升级Java
- jsr 303
- jsr 305
7.1.2 空对象模式
空对象模式,他的弊端在于需要创建一个特例对象,但是如果特例的情况比较多,我们是不是需要创建多个特例对象呢,虽然我们也使用了面向对象的多态性,但是业务复杂性如果让我们创建多个特里对象,还是要认真考虑,他可能带来代码的复杂性
可以使用Optional进行优化
7.1.3 Optional不能作为入参
7.1.4 Optional作为返回值
如果业务方强制要求,不要使用Optional,直接抛出异常并说明
只有当考虑它返回null是否合理的情况下才进行Optional的返回
返回空的集合而不是Optional
7.1.5 使用Optional变量
7.1.6 如何使用Optional