从HikariCP的性能优化说起!

共 7316字,需浏览 15分钟

 ·

2021-08-18 17:39

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

推荐:https://www.xttblog.com/?p=5265

你好,我是业余草,这是我的第 445 篇原创文章。

这篇文章我想了很久没想到好标题,索性就以《从HikariCP的性能优化说起!》为题开始吧!

这两天看到群里有人阅读到网上的文章,在群里问:“invokestatic 性能比 invokevirtual 好?”

一时间难倒了不少人,有人建议去看周老师的 JVM 书籍(深入理解Java虚拟机)中找答案,引起了群友广泛的讨论。不少人表示没看过,还有部分表示看不懂。

其实不看周老师的书,也能搞定这个问题。

同时,阿里巴巴出品的 Java 开发手册中也有一段这样的描述:

【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

这句话说的太模糊了,也不给一个详细的解释,看的云里雾里。很多人都看懵了,也包括我自己。没得办法,我尝试着从字节码的角度找找答案,结果还真被我找到了。

我们先来看一个 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 ++;
    }
}

通过类实例访问静态变量,阿里巴巴的规约插件同样的会报出红线提示。

阿里巴巴Java开发规约插件p3c提示

这个提示不影响代码运行。我们可以执行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
         0new           #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
        18return
              LineNumberTable:
        line 110
        line 128
        line 1318
      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
       4return

  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
       8return

你会发现指令变少了。除了少了创建对象的 new、dup、invokespecial 指令外,还多出了 astore_1、aload_1、pop 指令。

astore_1 指令:1 是代表在本地变量表(LocalVariableTable)中的序号位置。Slot 1 就是 xttblog 对象。它的意思就是,创建完对象后,把 xttblog 存入到本地变量表位置 1 中。

aload_1 指令:把存放在局部变量表中索引 1 位置的对象引用压入操作栈。

pop 指令:然后,pop 指令,又把栈顶给弹出了。

到现在,你应该看明白了。通过类实例去访问静态变量,来来回回的把 xttblog 对象给折腾了一遍。它多出了,存入本地变量表,压栈,出栈的操作。把本来利索的事情,给多加几道工序,反而影响运行效率。

其他的指令我就不讲了,具体可以查看我前面的文章:《JVM 常用指令速查手册,建议收藏!》。

JVM部分指令

总结一下,就是静态变量或方法的访问比通过类实例访问静态变量和方法快。同样的道理,invokestatic 性能比 invokevirtual 好,也就不足为奇了。

实际上,HikariCP 也利用了 Javassist 来生成委托实现动态代理,优化并精简了字节码。

invokevirtual vs invokestatic

具体官方英文文档,参考这里:https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole

HikariCP 的作者,也就是 Brett Wooldridge 大神也是通过字节码来说明,invokestatic 性能比 invokevirtual 好,同时 invokestatic 相比 invokevirtual 更容易被 JVM 优化调用。

HikariCP作者的性能优化

另外,优化后的(上图中的上半图)指令,还少了一个 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 好!

时间仓促,就先聊到这里吧,如有不懂或疑问,欢迎加我微信:codedq,进群学习!

浏览 48
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报