本博客来自我的新书Java系统性能优化(暂定名),也欢迎阅读我的新书 《Spring Boot 2 精髓 》
4.22.1 final无法帮助内联
有一个过时规则,“在java的getter和setter方法中经量使用final关键字,以支持内联“,比如
public final String getUserName(){
return this.userName;f
}
在Java中,final关键字可以用来修饰类、方法和变量,我们在第三章了解到final在并发的时候,修饰变量得时候具有的语义同volatile一样,内存可见性和禁止重排序。 final修饰的方法的时候,表示子类不能覆盖此方法。早期的Java版本,final关键字还可以提醒虚拟进行内联,但现在是否内联,不再需要使用final关键字。Aleksey在他的论文里,验证了final关键字对于方法是否内联并没有直接联系,The Black Magic of (Java) Method Dispatch,有兴趣的可以自己看一下,同样有一个JMH工程可以自己下载下来测试分析一下
关于内联,参考第8章JIT优化
4.22.2 subString 内存泄露
较早的Java性能优化书指出String.subString容易造成内存泄露,主要原因是JDK版本的subString方法,会复用String的数组,并没有为新的字符串生成一个新的char数组,这样,如果原来的String特别长,那么新返回的String所占用的空间也会特别大。JDK6以后已经不再subString的时候复用char数组了,而是改成重新生成一个数组,我们再第二章看到构造一个String,会重新生成一个字符串数组value
private final char value[];
public String(char value[], int offset, int count) {
.....
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
4.22.3 循环优化
有一种说法,嵌套循环中,嵌套循环应该遵循“外小内大”的原则,这样性能才会高,这种说法给出了俩个例子,第一个是外大内小
stratTime = System.nanoTime();
for (int i = 0; i < 100_000_00; i++) {
for (int j = 0; j < 10; j++) {
}
}
endTime = System.nanoTime();
System.out.println("外大内小耗时:"+ (endTime - stratTime));
外小内大
stratTime = System.nanoTime();
for (int i = 0; i <10 ; i++) {
for (int j = 0; j < 10000000; j++) {
}
}
endTime = System.nanoTime();
System.out.println("外小内大耗时:"+(endTime - stratTime));
俩个代码片段循环同样次数,但从测试结果看后者远大于前者,也就是嵌套循环外小内大的性能高于外大内小。
这种说法忽略了JIT会做DEAD-CODE消除,JIT判断循环对程序不会有任何影响而消除了循环体, 导致了结果测试不准,如果使用JMH测试,如下,就会发现嵌套循环结果一样
//ForDeadCodeTest.java
@Benchmark
public long test(Blackhole hole) {
long startTime = System.nanoTime();
int i=0,j=0;
for ( i = 0; i < 100_000_00; i++) {
for ( j = 0; j < 10; j++) {
hole.consume(j);
}
hole.consume(i);
}
Long endTime = System.nanoTime();
return endTime-startTime+i+j;
}
@Benchmark
public long tes2(Blackhole hole) {
long startTime = System.nanoTime();
int i=0,j=0;
for ( i = 0; i <10 ; i++) {
for ( j = 0; j < 100_000_00; j++) {
hole.consume(j);
}
hole.consume(i);
}
Long endTime = System.nanoTime();
return endTime-startTime+i+j;
}
//测试基准,空函数
@Benchmark
public long base(Blackhole hole) {
long startTime = System.nanoTime();
Long endTime = System.nanoTime();
return endTime-startTime;
}
这里使用了JMH提供的Blackhole函数来防止出现代码消除情况,这种测试最后证明,俩种循环性能没有区别
Benchmark Mode Score Units
c.i.c.c.ForDeadCodeTest.base avgt 0.000 ms/op
c.i.c.c.ForDeadCodeTest.tes2 avgt 56.007 ms/op
c.i.c.c.ForDeadCodeTest.test avgt 50.228 ms/op
4.22.4 循环中捕捉异常
一种说法是应该在循环体外捕捉异常,而不要在循环体内,理由try catch 代价较大。
for(int i=0;i<size;i++){
try{
}catch(Exception ex){
}
}
如上代码,这种说法建议改成循环体外
try{
for(int i=0;i<size;i++){
}
}catch(Exception ex){
}
实际上,循环体内使用try catch方式对性能几乎没什么影响,不需要关注在循环体内还需循环体外。按照自己业务需要正确使用try catch就行了。