你好,我是,这是我的第 445 篇原创文章。
这篇文章,我想了很久,没想到好名字。所以就随便起了一个,和群友保持一致!
这两天看到群里有人阅读到网上的文章,在群里问:“invokestatic 性能比 invokevirtual 好?”
一时间难倒了不少人,有人建议去看周老师的 JVM 书籍(深入理解Java虚拟机)中找答案,引起了群友广泛的讨论。不少人表示没看过,还有部分表示看不懂。
其实不看周老师的书,也能搞定这个问题。
同时,阿里巴巴出品的 Java 开发手册中也有一段这样的描述:
【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
https://github.com/alibaba/p3c/blob/master/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E5%B5%A9%E5%B1%B1%E7%89%88%EF%BC%89.pdf
这句话说的太模糊了,也不给一个详细的解释,看的云里雾里。很多人都看懵了,也包括我自己。没得办法,我尝试着从字节码的角度找找答案,结果还真被我找到了。
我们先来看一个 demo:
public class Xttblog {
public static int num = 0;
public void add(){
this.num ++;
}
}
这段代码,如果你安装了 p3c 插件,就会有爆红提示:
我们先忽略这个提示,写个 main 方法,看看字节码层面指令。
public class StaticTest {
public static void main(String[] args) {
Xttblog xttblog = new Xttblog();
xttblog.num ++;
}
}
通过类实例访问静态变量,阿里巴巴的规约插件同样的会报出红线提示。
这个提示不影响代码运行。我们可以执行javap
,不会用的先看java -help
,或者 idea 安装字节码插件:jclasslib。
Compiled from "StaticTest.java"
public class com.xttblog.test.StaticTest {
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/xttblog/Xttblog
3: dup
4: invokespecial #3 // Method com/xttblog/Xttblog."<init>":()V
7: astore_1
8: aload_1
9: pop
10: getstatic #4 // Field com/xttblog/Xttblog.num:I
13: iconst_1
14: iadd
15: putstatic #4 // Field com/xttblog/Xttblog.num:I
18: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
8 11 1 xttblog Lcom/xttblog/Xttblog;
}
我们再次更新一下 main 方法中的代码:
public class StaticTest {
public static void main(String[] args) {
/*Xttblog xttblog = new Xttblog();
xttblog.num++; */
Xttblog.num ++;
}
}
再一次的查看字节码。
Compiled from "StaticTest.java"
public class com.xttblog.test.StaticTest {
public com.xttblog.test.StaticTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field com/xttblog/test/Xttblog.num:I
3: iconst_1
4: iadd
5: putstatic #2 // Field com/xttblog/test/Xttblog.num:I
8: return
你会发现指令变少了。除了少了创建对象的 new、dup、invokespecial 指令外,还多出了 astore_1、aload_1、pop 指令。
astore_1 指令
:1 是代表在本地变量表(LocalVariableTable)中的序号位置。Slot 1 就是 xttblog 对象。它的意思就是,创建完对象后,把 xttblog 存入到本地变量表位置 1 中。
aload_1 指令
:把存放在局部变量表中索引 1 位置的对象引用压入操作栈。
pop 指令
:然后,pop 指令,又把栈顶给弹出了。
到现在,你应该看明白了。通过类实例去访问静态变量,来来回回的把 xttblog 对象给折腾
了一遍。它多出了,存入本地变量表,压栈,出栈的操作。把本来利索的事情,给多加几道工序,反而影响运行效率。
其他的指令我就不讲了,具体可以查看我前面的文章:《JVM 常用指令速查手册,建议收藏!》。
总结一下,就是静态变量或方法的访问比通过类实例访问静态变量和方法快
。同样的道理,invokestatic 性能比 invokevirtual 好,也就不足为奇了。
实际上,HikariCP 也利用了 Javassist 来生成委托实现动态代理,优化并精简了字节码。
具体官方英文文档,参考这里:https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole
。
HikariCP 的作者,也就是 Brett Wooldridge 大神也是通过字节码来说明,invokestatic 性能比 invokevirtual 好,同时 invokestatic 相比 invokevirtual 更容易被 JVM 优化调用。
另外,优化后的(上图中的上半图)指令,还少了一个 getstatic。同时栈大小从 5 个元素减少到 4 个元素。这是因为 invokevirtual 指令在堆栈上隐式传递 ProxyFactory 实例的情况下(即 this),并且在调用时从堆栈中需要额外(看不见)弹出 this。
读过周志明老师的《深入理解Java虚拟机》第二版的同学,可能还知道:invokevirtual 指令的调用依赖于运行时解析
,其解析过程大致分为以下几个步骤:
- 找到操作数栈顶的第一个元素(本例中就是 this)所指向的对象的实际类型,记作 C。
- 如果在类型 C 中找到了与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回 java.lang.IllegalAccessError 异常。
- 否则,按照继承关系对 C 的父类进行第 2 步的搜索和校验过程。
- 如果始终都没有找到合适的方法,则抛出 java.lang.AbstractMethodError 异常。
说白了,invokevirtual 会进行查找,查找重载函数,以及权限验证。找出类型和权限全符合的函数进行执行。
而 invokestatic 指令用于调用静态方法,即使用 static 关键字修饰的方法;static 修饰的方法是属于具体类的,因此不需要多余的查找和权限验证,因此 invokestatic 的性能比 invokevirtual 好!
: » invokestatic性能比invokevirtual好?
原创文章,作者:6024010,如若转载,请注明出处:https://blog.ytso.com/252296.html