继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

JDK9 ArrayList add方法的注释问题

wwpbjing
关注TA
已关注
手记 1
粉丝 3
获赞 0

今天看群里有小伙伴问JDK9 ArrayList的一个问题,我正好看到这里,感觉甚是有缘,隧研究一番,发给他.

JDK9+以后,ArrayList类的add(E e)方法调用了一个私有的add(E e, Object[] elementData, int s)的方法,

方法本身没什么,注释倒是挺有趣,先搬注释:

/**
 * This helper method split out from add(E) to keep method
 * bytecode size under 35 (the -XX:MaxInlineSize default value),
 * which helps when add(E) is called in a C1-compiled loop.
 */
private void add(E e, Object[] elementData, int s) {
    ...
}

先翻译:本工具方法是从add(E)方法中拆分出来的,旨在保持方法的字节码小于35字节(-XX:MaxInlineSize默认值),此举有助于帮助C1编译器循环调用add(E)方法时做优化

先弄白明里边的两个概念:

  • -XX:MaxInlineSize=<size>
    Integer specifying maximum number of bytecode instructions in a method which gets inlined. 

    翻译:设置可被优化的内联方法最大的字节码指令数

    我的理解:当一个方法翻译成字节码后,如果占用的字节码字节数<35,JIT(即时编译器)才会考虑使用内联(inline)优化,可以通过-XX:MaxInlineSize=<size>调整

    传送门:
    https://www.oracle.com/technetwork/java/javase/tech/exactoptions-jsp-141536.html

  • C1:

    解释C1前先来解释JIT(即时编译器,以下简称JIT):

    通常情况下,我们写的Java代码会被HotSpot虚拟机以解释字节码的方式解释执行(我们先忽略AOT和-Xcomp的情况),随着热点(被频繁调用的)代码预热,一些热点代码会被JIT动态的编译为本地机器码

    HotSpot虚拟机包含多个JIT————C1、C2

    C1编译速度较快,C2编译速度较慢,C2会在运行时收集运行时数据(profile),因此C2产出的代码会比C1产出的代码运行效率更高一些

    C1、C2在编译本地机器码时可以为内联方法做优化,即:产出更好的本地机器码,因此JDK的ArrayList中add方法才做了开头注释中写的优化,即:拆分成短小的方法

我们做个实验验证下:add方法小于35个字节:

public class ArrayListLearning {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(Integer.valueOf(i + 1));
        }
    }
}

运行时加上参数:

-XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+LogCompilation -XX:TieredStopAtLevel=1 -XX:+PrintCompilation
eg:java9/java10 -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+LogCompilation -XX:TieredStopAtLevel=1 -XX:+PrintCompilation package.class

我们来看下这些参数是什么东东:

  • -XX:TieredStopAtLevel
    由于jdk7以后采用分层编译,-client -server已经无法指定使用C1/C2了,使用-XX:TieredStopAtLevel=1告诉JIT使用C1编译
  • -XX:+PrintCompilation
    控制台打印JIT编译字节码类名和方法字节数
  • -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions
    解锁实验特性,可以用一些JDK开发人员使用的debug功能,是不是很厉害
  • -XX:+LogCompilation
    将JIT编译输出的详细信息打印到hotspotXXXX.log文件中

    运行后,我们看下我截取的重要的片段

    ...
    <task_queued compile_id='121' method='java.util.ArrayList add (Ljava/lang/Object;)Z' bytes='25' count='256' iicount='256' level='1' stamp='0.259' comment='tiered' hot_count='256'/>
    <task_queued compile_id='122' method='java.util.ArrayList add (Ljava/lang/Object;[Ljava/lang/Object;I)V' bytes='23' count='256' iicount='256' level='1' stamp='0.259' comment='tiered' hot_count='256'/>
    ...
    从日志可以看出:
    add (Ljava/lang/Object;)->add(E e)占用25个字节
    add (Ljava/lang/Object;[Ljava/lang/Object;I)->add(E e, Object[] elementData, int s)占用23个字节,[Ljava/lang/Object是Object数组,I整型,证明确实小于35个字节

至于内联方法JIT优化细节,具体本地机器码生成及反汇编,我懒得再弄了,读者要是有兴趣可以尝试下面的过程

1、编译一个debug版本的(slow-debug)openjdk

2、使用-XX:+PrintOptoAssembly参数(可以在hotspotXXXX.log文件中查看JIT优化代码)分别实验内联优化前后代码区别,得出JIT对内联到底做了哪些优化

全文完

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP