java 静态方法调用快_Java中static、private、public 方法哪个更快

本文是我多年之前的老博客(android-performance.com)的一篇文章,老博客很久没有维护了,把一些有用的文章转移过来。

在java中,同样的方法被声明不通的类型在访问速度上会有不同吗?如果不通会有多大差异?让我们功过实验来证明这一切。

我们有下面三段代码,运算逻辑相同,我们分别用static, private, public 来声明,然后分别对他们的运行时间:

public class TestStatic {

static long add(long a, long b) {

return a + b;

}

public static void main(String[] args) {

long start = System.currentTimeMillis();

for (long i = 0; i < 9999999999L; i++) {

add(i, i + 1);

}

System.out.println(System.currentTimeMillis() - start);

}

}

public class TestPrivate {

private long add(long a, long b) {

return a + b;

}

public static void main(String[] args) {

TestPrivate obj = new TestPrivate();

long start = System.currentTimeMillis();

for (long i = 0; i < 9999999999L; i++) {

obj.add(i, i + 1);

}

System.out.println(System.currentTimeMillis() - start);

}

}

public class TestPublic {

public long add(long a, long b) {

return a + b;

}

public static void main(String[] args) {

TestPublic obj = new TestPublic();

long start = System.currentTimeMillis();

for (long i = 0; i < 9999999999L; i++) {

obj.add(i, i + 1);

}

System.out.println(System.currentTimeMillis() - start);

}

}

表1:各方法执行5次所花时间的对比结果(单位毫秒)运行环境是在我以前的旧笔记本上(Dell E6410 上)

#

static 方法

private 方法

public 方法

1

16804

20424

20428

2

17061

20291

20246

3

17044

20629

20604

4

17064

20207

21107

5

16869

20079

20405

从结果中可见,static 方法比 private 和 public 方法要快 15% 左右,private 和 public 消耗相差无几。

通过 javap -v 获得的字节码我们看到,在调用这几个方发的时候,jvm 使用了不同的指令:

static 实现中 main 方法的部分字节码:

...

6: goto 21

9: lload3

10: lload3

11: lconst1

12: ladd

13: invokestatic #27 // Method add:(JJ)J

16: pop2

17: lload3

18: lconst1

19: ladd

20: lstore3

21: lload_3

...

private 实现中 main 方法的部分字节码:

...

15: goto 35

18: aload1

19: lload 4

21: lload 4

23: lconst1

24: ladd

25: invokespecial #28 // Method add:(JJ)J

28: pop2

29: lload 4

31: lconst_1

32: ladd

33: lstore 4

35: lload 4

...

public 实现中 main 方法的部分字节码:

...

15: goto 35

18: aload1

19: lload 4

21: lload 4

23: lconst1

24: ladd

25: invokevirtual #28 // Method add:(JJ)J

28: pop2

29: lload 4

31: lconst_1

32: ladd

33: lstore 4

35: lload 4

...

在看一下几种实现方式的add方法的字节码:

static 实现中 add 方法的字节码:

static long add(long, long);

flags: ACCSTATIC

Code:

stack=4, locals=4, argssize=2

0: lload0

1: lload2

2: ladd

3: lreturn

LineNumberTable:

line 6: 0

LocalVariableTable:

Start Length Slot Name Signature

0 4 0 a J

0 4 2 b J

private 实现中 add 方法的部分字节码(需要用javap -v -p):

private long add(long, long);

flags: ACCPRIVATE

Code:

stack=4, locals=5, argssize=3

0: lload1

1: lload3

2: ladd

3: lreturn

LineNumberTable:

line 8: 0

LocalVariableTable:

Start Length Slot Name Signature

0 4 0 this LTestPrivate;

0 4 1 a J

0 4 3 b J

public 实现中 add 方法的部分字节码:

public long add(long, long);

flags: ACCPUBLIC

Code:

stack=4, locals=5, argssize=3

0: lload1

1: lload3

2: ladd

3: lreturn

LineNumberTable:

line 6: 0

LocalVariableTable:

Start Length Slot Name Signature

0 4 0 this LTestPublic;

0 4 1 a J

0 4 3 b J

可以看到几个 add 方法字节码(Code部分)的实现几乎是一样的,而在调用这几个方法时jvm使用了 invokestatic,invokespecial 和 invokevirtual 三种不同的虚拟机指令。表1 中的性能差异主要就是由这几条指令的操作方式所决定的,invokestatic 指令是基于方法(在编译时就知道该调用哪个方法)的指令,在进行栈帧切换(可以理解方法切换)时只需要把方法的参数入栈即可,从 “static 实现中 add 方法的字节码” 中我们可以看到其 LocalVariableTable(局部变量表)中只有a、b两个值。而 invokespecial 和 invokevirtual 是基于实例的指令,他们处理把a、b两个参数入栈之外,还要把对实例的引用(this 指针)也同时入栈,所以在private和public实现方式的add方法字节码中,LocalVariableTable 还包括了this指针。所以这一点点额外的操作就决定了他们的性能差别。

更多关于字节码的解释可参考之前的一篇文章 《读懂 javap -verbose 》

关于 invokestatic 、invokespecial、invokevirtual 这几个指令的详解,可参考相关官方文档

invokespecial 和 invokevirtual 在上面的例子并没有体现出明显的差别。我们再举一个例子比较一下,在这里例子中我们引入多态特性。我们把 TestPublic 改造一下,代码如下:

class TestBaseClass {

public long add(long a, long b) {

return a + b;

}

}

public class TestPublic extends TestBaseClass {

public long add(long a, long b) {

return a + b;

}

public static void main(String[] args) {

TestBaseClass obj = new TestPublic();

long start = System.currentTimeMillis();

for (long i = 0; i < 9999999999L; i++) {

obj.add(i, i + 1);

}

System.out.println(System.currentTimeMillis() - start);

}

}

我们在执行5次,看看结果是怎样的(单位毫秒)

1

2

3

4

5

79712

80419

81648

89341

83449

在 public 多态的情况下,同样的逻辑,花的时间是之前的4倍左右。这是由于 invokevirtual 指令属于“动态绑定”——即运行时才知道方法的所属类型是哪个,相对于动态绑定的是“静态绑定”——即编译时就知道要执行的方法属于哪个类。动态绑定不仅需要查方法表,而需要在运行时确定要引用的方法所属的类到底是哪个,这两中操作是比较耗时的。

小结

本文锁讲述的内容并不会对你的实际项目有多大的性能提升,但是却可以指导我们养成一个“好”的编码习惯。对于独立的逻辑优先使用 static 方式或者是 private 方式(而且也符合面向对象的 OCP 原则),没有必要的情况下,少用 public 方法,尤其在多态的模式下,public 方法会有比较大的性能损耗。 因为在 java 中 invokestatic 、invokespecial 都属于静态绑定,其他的静态绑定还有声明为 final 的方法,他们在编译时就知道方法属于那个类,所以在运行时会比较快地定位到方法在内存中对应的字节码地址(在方法区中),不像动态绑定,还需要明确方法所在的类型并搜索方法表才能定位到。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值