为了账号安全,请及时绑定邮箱和手机立即绑定

JDK9 ArrayList add方法的注释问题

标签:
Java

今天看群里有小伙伴问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对内联到底做了哪些优化

全文完

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
3
获赞与收藏
0

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消