请不要再说Java中final方法比非final性能更好了

无继承

有 static 修饰

static final

  1. // 生成随机数字和字母,
  2.    publicstaticfinalString getStringRandomFinal(int length){
  3.        String val ="";
  4.        Random random =newRandom();
  5.        // 参数length,表示生成几位随机数
  6.        for(int i =0; i < length; i++){
  7.            String charOrNum = random.nextInt(2)%2==0?"char":"num";
  8.            // 输出字母还是数字
  9.            if("char".equalsIgnoreCase(charOrNum)){
  10.                // 输出是大写字母还是小写字母
  11.                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
  12.                val +=(char)(random.nextInt(26)+97);
  13.            }elseif("num".equalsIgnoreCase(charOrNum)){
  14.                val +=String.valueOf(random.nextInt(10));
  15.            }
  16.        }
  17.        return val;
  18.    }

static 非 final

  1. // 生成随机数字和字母,
  2.    publicstaticString getStringRandom(int length){
  3.        String val ="";
  4.        Random random =newRandom();
  5.        // 参数length,表示生成几位随机数
  6.        for(int i =0; i < length; i++){
  7.            String charOrNum = random.nextInt(2)%2==0?"char":"num";
  8.            // 输出字母还是数字
  9.            if("char".equalsIgnoreCase(charOrNum)){
  10.                // 输出是大写字母还是小写字母
  11.                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
  12.                val +=(char)(random.nextInt(26)+97);
  13.            }elseif("num".equalsIgnoreCase(charOrNum)){
  14.                val +=String.valueOf(random.nextInt(10));
  15.            }
  16.        }
  17.        return val;
  18.    }

结果

这里使用了 OpenJDK 的 JMH 基准测试工具来测试的,结果如下:
  1. # JMH 1.4.1 (released 903 days ago, please consider updating!)
  2. # VM invoker: /srv/jdk1.8.0_92/jre/bin/java
  3. # VM options: <none>
  4. # Warmup: 20 iterations, 1 s each
  5. # Measurement: 20 iterations, 1 s each
  6. # Timeout: 10 min per iteration
  7. # Threads: 1 thread, will synchronize iterations
  8. # Benchmark mode: Throughput, ops/time
  9. # Benchmark: org.agoncal.sample.jmh.Main.benchmark
  10. 中间忽略了预热及测试过程,这里只显示结果
  11. Result:206924.113±(99.9%)7746.446 ops/s [Average]
  12.  Statistics:(min, avg, max)=(132107.466,206924.113,267265.397), stdev =32798.937
  13.  Confidence interval (99.9%):[199177.667,214670.559]
  14. # JMH 1.4.1 (released 903 days ago, please consider updating!)
  15. # VM invoker: /srv/jdk1.8.0_92/jre/bin/java
  16. # VM options: <none>
  17. # Warmup: 20 iterations, 1 s each
  18. # Measurement: 20 iterations, 1 s each
  19. # Timeout: 10 min per iteration
  20. # Threads: 1 thread, will synchronize iterations
  21. # Benchmark mode: Throughput, ops/time
  22. # Benchmark: org.agoncal.sample.jmh.Main.benchmarkFinal
  23. 中间忽略了预热及测试过程,这里只显示结果
  24. Result:210111.568±(99.9%)8486.176 ops/s [Average]
  25.  Statistics:(min, avg, max)=(133813.368,210111.568,267525.228), stdev =35931.001
  26.  Confidence interval (99.9%):[201625.392,218597.744]
  27. # Run complete. Total time: 00:13:54
  28. Benchmark                       Mode  Samples       Score      Error  Units
  29. o.a.s.j.Main.benchmark         thrpt      200  206924.113±7746.446  ops/s
  30. o.a.s.j.Main.benchmarkFinal    thrpt      200  210111.568±8486.176  ops/s
总结:你说final的性能比非final有没有提升呢?可以说有,但几乎可以忽略不计。如果单纯地追求性能,而将所有的方法修改为 final 的话,我认为这样子是不可取的。
而且这性能的差别,远远也没有网上有些人说的提升 50% 这么恐怖(有可能他们使用的是10年前的JVM来测试的吧^_^,比如 《35+ 个 Java 代码性能优化总结》这篇文章。雷总:不服?咱们来跑个分!)

分析

字节码级别的差别

StringKit.java StringKitFinal.java

它们在字节码上的差别:
  1. [18:52:08] emacsist:target $ diff /tmp/stringkit.log /tmp/stringkit-final.log
  2. 1,5c1,5
  3. <Classfile/Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKit.class
  4. <   Last modified 2017-6-15; size 1098 bytes
  5. <   MD5 checksum fe1ccdde26107e4037afc54c780f2c95
  6. <   Compiledfrom"StringKit.java"
  7. <publicclass org.agoncal.sample.jmh.StringKit
  8. ---
  9. >Classfile/Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKitFinal.class
  10. >   Last modified 2017-6-15; size 1118 bytes
  11. >   MD5 checksum 410f8bf0eb723b794e4754c6eb8b9829
  12. >   Compiledfrom"StringKitFinal.java"
  13. >publicclass org.agoncal.sample.jmh.StringKitFinal
  14. 24c24
  15. <   #15 = Class              #52            // org/agoncal/sample/jmh/StringKit
  16. ---
  17. >   #15 = Class              #52            // org/agoncal/sample/jmh/StringKitFinal
  18. 32,33c32,33
  19. <   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKit;
  20. <   #24 = Utf8               getStringRandom
  21. ---
  22. >   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKitFinal;
  23. >   #24 = Utf8               getStringRandomFinal
  24. 47c47
  25. <   #38 = Utf8               StringKit.java
  26. ---
  27. >   #38 = Utf8               StringKitFinal.java
  28. 61c61
  29. <   #52 = Utf8               org/agoncal/sample/jmh/StringKit
  30. ---
  31. >   #52 = Utf8               org/agoncal/sample/jmh/StringKitFinal
  32. 75c75
  33. <   public org.agoncal.sample.jmh.StringKit();
  34. ---
  35. >   public org.agoncal.sample.jmh.StringKitFinal();
  36. 87c87
  37. <             0       5     0  this   Lorg/agoncal/sample/jmh/StringKit;
  38. ---
  39. >             0       5     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;
  40. 89c89
  41. <   publicstatic java.lang.String getStringRandom(int);
  42. ---
  43. >   publicstaticfinal java.lang.String getStringRandomFinal(int);
  44. 91c91
  45. <     flags: ACC_PUBLIC, ACC_STATIC
  46. ---
  47. >     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
  48. 187c187
  49. <SourceFile:"StringKit.java"
  50. ---
  51. >SourceFile:"StringKitFinal.java"
可以看到除了方法名和方法修饰符不同之外,其他的没有什么区别了。

在调用者上面的字节码差别

  1. publicvoid benchmark();
  2.    descriptor:()V
  3.    flags: ACC_PUBLIC
  4.    Code:
  5.      stack=1, locals=1, args_size=1
  6.         0: bipush        32
  7.         2: invokestatic  #2                  // Method org/agoncal/sample/jmh/StringKit.getStringRandom:(I)Ljava/lang/String;
  8.         5: pop
  9.         6:return
  10.      LineNumberTable:
  11.        line 21:0
  12.        line 22:6
  13.      LocalVariableTable:
  14.        Start  Length  Slot  Name   Signature
  15.            0       7     0  this   Lorg/agoncal/sample/jmh/Main;
  16.    RuntimeVisibleAnnotations:
  17.      0:#26()
  18.  publicvoid benchmarkFinal();
  19.    descriptor:()V
  20.    flags: ACC_PUBLIC
  21.    Code:
  22.      stack=1, locals=1, args_size=1
  23.         0: bipush        32
  24.         2: invokestatic  #3                  // Method org/agoncal/sample/jmh/StringKitFinal.getStringRandomFinal:(I)Ljava/lang/String;
  25.         5: pop
  26.         6:return
  27.      LineNumberTable:
  28.        line 26:0
  29.        line 27:6
  30.      LocalVariableTable:
  31.        Start  Length  Slot  Name   Signature
  32.            0       7     0  this   Lorg/agoncal/sample/jmh/Main;
  33.    RuntimeVisibleAnnotations:
  34.      0:#26()
可以看到,它们在调用者上面的字节码也没有什么区别,只是方法名不一样之外。
对于 JVM 来说,它是只认字节码的,既然字节码除了方法名和修饰符一样,其他都一样,那就可以大概推测它们的性能几乎可以忽略不计了。因为调用 static final 和 static 非 final 的JVM指令是一样。

无 static 修饰

方法体是一样的,只是将它们删除了 static 的修饰。

结果

  1. # JMH version: 1.19
  2. # VM version: JDK 1.8.0_92, VM 25.92-b14
  3. # VM invoker: /srv/jdk1.8.0_92/jre/bin/java
  4. # VM options: <none>
  5. # Warmup: 20 iterations, 1 s each
  6. # Measurement: 20 iterations, 1 s each
  7. # Timeout: 10 min per iteration
  8. # Threads: 1 thread, will synchronize iterations
  9. # Benchmark mode: Throughput, ops/time
  10. # Benchmark: org.agoncal.sample.jmh.Main.benchmark
  11. 中间忽略了预热及测试过程,这里只显示结果
  12. Result"org.agoncal.sample.jmh.Main.benchmark":
  13.  201306.770±(99.9%)8184.423 ops/s [Average]
  14.  (min, avg, max)=(131889.934,201306.770,259928.172), stdev =34653.361
  15.  CI (99.9%):[193122.347,209491.193](assumes normal distribution)
  16. # JMH version: 1.19
  17. # VM version: JDK 1.8.0_92, VM 25.92-b14
  18. # VM invoker: /srv/jdk1.8.0_92/jre/bin/java
  19. # VM options: <none>
  20. # Warmup: 20 iterations, 1 s each
  21. # Measurement: 20 iterations, 1 s each
  22. # Timeout: 10 min per iteration
  23. # Threads: 1 thread, will synchronize iterations
  24. # Benchmark mode: Throughput, ops/time
  25. # Benchmark: org.agoncal.sample.jmh.Main.benchmarkFinal
  26. 中间忽略了预热及测试过程,这里只显示结果
  27. Result"org.agoncal.sample.jmh.Main.benchmarkFinal":
  28.  196871.022±(99.9%)8595.719 ops/s [Average]
  29.  (min, avg, max)=(131182.268,196871.022,265522.769), stdev =36394.814
  30.  CI (99.9%):[188275.302,205466.741](assumes normal distribution)
  31. # Run complete. Total time: 00:13:35
  32. Benchmark             Mode  Cnt       Score      Error  Units
  33. Main.benchmark       thrpt  200  201306.770±8184.423  ops/s
  34. Main.benchmarkFinal  thrpt  200  196871.022±8595.719  ops/s

分析

字节码级别的差别

  1. [19:20:17] emacsist:target $ diff /tmp/stringkit.log /tmp/stringkit-final.log
  2. 1,5c1,5
  3. <Classfile/Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKit.class
  4. <   Last modified 2017-6-15; size 1110 bytes
  5. <   MD5 checksum f61144e86f7c17dc5d5f2b2d35fac36d
  6. <   Compiledfrom"StringKit.java"
  7. <publicclass org.agoncal.sample.jmh.StringKit
  8. ---
  9. >Classfile/Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKitFinal.class
  10. >   Last modified 2017-6-15; size 1130 bytes
  11. >   MD5 checksum 15ce17ee17fdb5f4721f0921977b1e69
  12. >   Compiledfrom"StringKitFinal.java"
  13. >publicclass org.agoncal.sample.jmh.StringKitFinal
  14. 24c24
  15. <   #15 = Class              #52            // org/agoncal/sample/jmh/StringKit
  16. ---
  17. >   #15 = Class              #52            // org/agoncal/sample/jmh/StringKitFinal
  18. 32,33c32,33
  19. <   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKit;
  20. <   #24 = Utf8               getStringRandom
  21. ---
  22. >   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKitFinal;
  23. >   #24 = Utf8               getStringRandomFinal
  24. 47c47
  25. <   #38 = Utf8               StringKit.java
  26. ---
  27. >   #38 = Utf8               StringKitFinal.java
  28. 61c61
  29. <   #52 = Utf8               org/agoncal/sample/jmh/StringKit
  30. ---
  31. >   #52 = Utf8               org/agoncal/sample/jmh/StringKitFinal
  32. 75c75
  33. <   public org.agoncal.sample.jmh.StringKit();
  34. ---
  35. >   public org.agoncal.sample.jmh.StringKitFinal();
  36. 87c87
  37. <             0       5     0  this   Lorg/agoncal/sample/jmh/StringKit;
  38. ---
  39. >             0       5     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;
  40. 89c89
  41. <   public java.lang.String getStringRandom(int);
  42. ---
  43. >   publicfinal java.lang.String getStringRandomFinal(int);
  44. 91c91
  45. <     flags: ACC_PUBLIC
  46. ---
  47. >     flags: ACC_PUBLIC, ACC_FINAL
  48. 169c169
  49. <             0     125     0  this   Lorg/agoncal/sample/jmh/StringKit;
  50. ---
  51. >             0     125     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;
  52. 188c188
  53. <SourceFile:"StringKit.java"
  54. ---
  55. >SourceFile:"StringKitFinal.java"
可以看到,字节码上除了名字和 final 修饰符差别外,其余的是一样的。

在调用者上面的字节码差别

  1. publicvoid benchmark();
  2.    descriptor:()V
  3.    flags: ACC_PUBLIC
  4.    Code:
  5.      stack=2, locals=1, args_size=1
  6.         0:new           #2                  // class org/agoncal/sample/jmh/StringKit
  7.         3: dup
  8.         4: invokespecial #3                  // Method org/agoncal/sample/jmh/StringKit."<init>":()V
  9.         7: bipush        32
  10.         9: invokevirtual #4                  // Method org/agoncal/sample/jmh/StringKit.getStringRandom:(I)Ljava/lang/String;
  11.        12: pop
  12.        13:return
  13.      LineNumberTable:
  14.        line 21:0
  15.        line 22:13
  16.      LocalVariableTable:
  17.        Start  Length  Slot  Name   Signature
  18.            0      14     0  this   Lorg/agoncal/sample/jmh/Main;
  19.    RuntimeVisibleAnnotations:
  20.      0:#30()
  21.  publicvoid benchmarkFinal();
  22.    descriptor:()V
  23.    flags: ACC_PUBLIC
  24.    Code:
  25.      stack=2, locals=1, args_size=1
  26.         0:new           #5                  // class org/agoncal/sample/jmh/StringKitFinal
  27.         3: dup
  28.         4: invokespecial #6                  // Method org/agoncal/sample/jmh/StringKitFinal."<init>":()V
  29.         7: bipush        32
  30.         9: invokevirtual #7                  // Method org/agoncal/sample/jmh/StringKitFinal.getStringRandomFinal:(I)Ljava/lang/String;
  31.        12: pop
  32.        13:return
  33.      LineNumberTable:
  34.        line 26:0
  35.        line 27:13
  36.      LocalVariableTable:
  37.        Start  Length  Slot  Name   Signature
  38.            0      14     0  this   Lorg/agoncal/sample/jmh/Main;
  39.    RuntimeVisibleAnnotations:
  40.      0:#30()
可以看到,它们除了名字不同之外,其他的JVM指令都是一样的。

总结

对于是否有 final 修饰的方法,对性能的影响可以忽略不计。因为它们生成的字节码除了 flags 标志位是否有 final 修饰不同之外,其他所有的JVM指令,都是一样的(对于方法本身,以及调用者本身的字节码都一样)。对于JVM来说,它执行的就是字节码,如果字节码都一样的话,那对于JVM来说,它就是同一样东西的了。

有继承

无 final 修饰

  1. package org.agoncal.sample.jmh;
  2. import java.util.Random;
  3. /**
  4. * Created by emacsist on 2017/6/15.
  5. */
  6. publicabstractclassStringKitAbs{
  7.    // 生成随机数字和字母,
  8.    publicString getStringRandom(int length){
  9.        String val ="";
  10.        Random random =newRandom();
  11.        // 参数length,表示生成几位随机数
  12.        for(int i =0; i < length; i++){
  13.            String charOrNum = random.nextInt(2)%2==0?"char":"num";
  14.            // 输出字母还是数字
  15.            if("char".equalsIgnoreCase(charOrNum)){
  16.                // 输出是大写字母还是小写字母
  17.                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
  18.                val +=(char)(random.nextInt(26)+97);
  19.            }elseif("num".equalsIgnoreCase(charOrNum)){
  20.                val +=String.valueOf(random.nextInt(10));
  21.            }
  22.        }
  23.        return val;
  24.    }
  25. }

有 final 修饰

  1. package org.agoncal.sample.jmh;
  2. import java.util.Random;
  3. /**
  4. * Created by emacsist on 2017/6/15.
  5. */
  6. publicabstractclassStringKitAbsFinal{
  7.    // 生成随机数字和字母,
  8.    publicfinalString getStringRandomFinal(int length){
  9.        String val ="";
  10.        Random random =newRandom();
  11.        // 参数length,表示生成几位随机数
  12.        for(int i =0; i < length; i++){
  13.            String charOrNum = random.nextInt(2)%2==0?"char":"num";
  14.            // 输出字母还是数字
  15.            if("char".equalsIgnoreCase(charOrNum)){
  16.                // 输出是大写字母还是小写字母
  17.                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
  18.                val +=(char)(random.nextInt(26)+97);
  19.            }elseif("num".equalsIgnoreCase(charOrNum)){
  20.                val +=String.valueOf(random.nextInt(10));
  21.            }
  22.        }
  23.        return val;
  24.    }
  25. }

测试代码

写一个类来继承上面的抽象类,以此来测试在继承中 final 有否对多态中的影响
  1. package org.agoncal.sample.jmh;
  2. /**
  3. * Created by emacsist on 2017/6/15.
  4. */
  5. publicclassStringKitFinalextendsStringKitAbsFinal{
  6. }

  1. package org.agoncal.sample.jmh;
  2. /**
  3. * Created by emacsist on 2017/6/15.
  4. */
  5. publicclassStringKitextendsStringKitAbs{
  6. }
然后在基准测试中:
  1. @Benchmark
  2.    publicvoid benchmark(){
  3.        newStringKit().getStringRandom(32);
  4.    }
  5.    @Benchmark
  6.    publicvoid benchmarkFinal