报告中,以2(这是使用了uniq -c 对相同的信息计算数量的结果)最为起始的条目说明这些方法在Java 7 和Java 8 中起字节码大小没有改变。虽然这并不能完全肯定地说明这些方法的字节码没有改变,但通常我们也可以视为没有改变。重复次数为1的方法有如下的情况:
a)方法的字节码已经改变。
b)这些方法为新的方法。
我们看一下以1开始的行数据
1 "java.lang.invoke.AbstractValidatingLambdaMetafactory","void validateMetafactoryArgs()",864 1 "java.lang.invoke.InnerClassLambdaMetafactory","private java.lang.Class spinInnerClass()",497 1 "java.lang.reflect.Executable","java.lang.String sharedToGenericString(int,boolean)",329上面三个对内联不友好的方法全部来自Java 8,因此这属于新方法的情况。前两个方法与lamda表达式实现相关,第三个方法和反射子系统中继承层级调整有关。在这里,这个改变就是在Java 8 中引入了方法和构造器可以继承的通用基类。
最后,我们看一看JDK核心库一些令人惊讶的特性:
$ grep -i ^\"java.lang.String large_jre_methods_8u25.txt "java.lang.String","public java.lang.String[] split(java.lang.String,int)",326 "java.lang.String","public java.lang.String toLowerCase(java.util.Locale)",431 "java.lang.String","public java.lang.String toUpperCase(java.util.Locale)",439从上面的日志我们可以了解到,即使是Java 8 中一些java.lang.String中一些关键的方法还是处于内联不友好的状态。尤其是toLowerCase和toUpperCase这两个方法居然过大而无法内联,着实让人感到奇怪。但是,这两个方法由于要处理UTF-8数据而不是简单的ASCII数据,进而增加了方法的复杂性和大小,因而超过了内联友好的临界值。
对于性能要求较高并且确定只处理ASCII数据的程序,通常我们需要实现一个自己的StringUtils类。该类中包含一些静态的方法来实现上述内联不友好的方法的功能,但这些静态方法既保持紧凑型又能到达内联的要求。
上述我们讨论的改进都是大部分基于静态分析。除此之外,使用强大的JITWatch工具可以帮助我们更好地优化。JITWatch工具需要设置-XX:+LogCompilation选项开启日志打印。其打印出来的日志为XML格式,而非PrintCompilation简单的文本输出,并且这些日志比较大,通常会到达几百MB。它会影响正在运行的程序(默认情况下主要来自日志输出的影响),因此这个选项不适合在线上的生产环境使用。
PrintCompilation和Jarscan结合使用并不困难,但却提供了简单且很有实际作用的一步,尤其是对于开发团队打算研究其程序中即时编译执行情况时。大多数情况下,在性能优化中,一个快速的分析可以帮助我们完成一些容易实现的目标。
关于作者Ben Evans是jClarity公司的CEO,jClarity是一家致力于Java和JVM性能分析研究的创业公司。除此之外他还是London Java Community的负责人之一并在Java Community Process Executive Committee有一席之地。他之前的项目有Google IPO性能测试,金融交易系统,90年代知名电影网站等。