本博客来自我的新书Java系统性能优化(暂定名),也欢迎阅读我的新书 《Spring Boot 2 精髓 》
4.22.1 final无法帮助内联
有一个过时规则,“在java的getter和setter方法中经量使用final关键字,以支持内联“,比如
1public final String getUserName(){ 2 return this.userName;f 3}
在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
1private final char value[]; 2public String(char value[], int offset, int count) { 3 ..... 4 this.value = Arrays.copyOfRange(value, offset, offset+count); 5}
4.22.3 循环优化
有一种说法,嵌套循环中,嵌套循环应该遵循“外小内大”的原则,这样性能才会高,这种说法给出了俩个例子,第一个是外大内小
1stratTime = System.nanoTime(); 2for (int i = 0; i < 100_000_00; i++) { 3 for (int j = 0; j < 10; j++) { 4 5 } 6} 7endTime = System.nanoTime(); 8System.out.println("外大内小耗时:"+ (endTime - stratTime));
外小内大
1stratTime = System.nanoTime(); 2for (int i = 0; i <10 ; i++) { 3 for (int j = 0; j < 10000000; j++) { 4 } 5} 6endTime = System.nanoTime(); 7System.out.println("外小内大耗时:"+(endTime - stratTime)); 8
俩个代码片段循环同样次数,但从测试结果看后者远大于前者,也就是嵌套循环外小内大的性能高于外大内小。
这种说法忽略了JIT会做DEAD-CODE消除,JIT判断循环对程序不会有任何影响而消除了循环体, 导致了结果测试不准,如果使用JMH测试,如下,就会发现嵌套循环结果一样
1//ForDeadCodeTest.java 2@Benchmark 3public long test(Blackhole hole) { 4 long startTime = System.nanoTime(); 5 int i=0,j=0; 6 for ( i = 0; i < 100_000_00; i++) { 7 for ( j = 0; j < 10; j++) { 8 hole.consume(j); 9 } 10 hole.consume(i); 11 } 12 13 Long endTime = System.nanoTime(); 14 return endTime-startTime+i+j; 15} 16 17 18@Benchmark 19public long tes2(Blackhole hole) { 20 long startTime = System.nanoTime(); 21 int i=0,j=0; 22 for ( i = 0; i <10 ; i++) { 23 for ( j = 0; j < 100_000_00; j++) { 24 hole.consume(j); 25 } 26 hole.consume(i); 27 } 28 Long endTime = System.nanoTime(); 29 return endTime-startTime+i+j; 30} 31//测试基准,空函数 32@Benchmark 33public long base(Blackhole hole) { 34 long startTime = System.nanoTime(); 35 36 Long endTime = System.nanoTime(); 37 return endTime-startTime; 38}
这里使用了JMH提供的Blackhole函数来防止出现代码消除情况,这种测试最后证明,俩种循环性能没有区别
1Benchmark Mode Score Units 2c.i.c.c.ForDeadCodeTest.base avgt 0.000 ms/op 3c.i.c.c.ForDeadCodeTest.tes2 avgt 56.007 ms/op 4c.i.c.c.ForDeadCodeTest.test avgt 50.228 ms/op
4.22.4 循环中捕捉异常
一种说法是应该在循环体外捕捉异常,而不要在循环体内,理由try catch 代价较大。
1for(int i=0;i<size;i++){ 2 try{ 3 4 }catch(Exception ex){ 5 6 } 7}
如上代码,这种说法建议改成循环体外
1try{ 2 for(int i=0;i<size;i++){ 3 4 } 5}catch(Exception ex){ 6} 7
实际上,循环体内使用try catch方式对性能几乎没什么影响,不需要关注在循环体内还需循环体外。按照自己业务需要正确使用try catch就行了。