今天看群里有小伙伴问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 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦