String源码中的"avoid getfield opcode"

时间:2021-01-27 10:31:58

引言:

之前一篇文章梳理了String的不变性原则,还提到了一段源码中注释"avoid getfield opcode",当时通过查阅资料发现,这是为了防止 getfield(获取指定类的实例域,并将其值压入到栈顶)这个操作码的执行,这篇文章想从字节码的角度去分析一下。


先看一段代码吧

/**
* Created by chenqimiao on 16/11/29.
*/
public class Main { public char[] chars = new char[10]; public void test() {
System.out.println(chars[0]);
System.out.println(chars[1]);
System.out.println(chars[2]);
} public static void main(String args[]) {
Main m = new Main();
m.test();
}
}

  执行 javap -c Main ,分析一下字节码,查看一下分析后生成的操作码:

String源码中的"avoid getfield opcode"

查阅"深入理解JVM虚拟机"一书中的虚拟机字节码指令表,试着分析一下test方法下面的code.

4.获取指定的实例域,并将其值压入栈顶

7.将int型0推送至栈顶

8.将char数组指定索引的值推至栈顶

9.调用实例方法

总结一下的话,就是每次获取实例域 推到栈顶,然后推被操作的索引到栈顶,然后取到对应数组的指定索引的值推到栈顶,然后就是调用输出方法了。可以看到输出三次,getfield操作码就调用了三次,假想我们在遍历这个char数组,那象上述写法,要频繁调用getfield操作码了。

我们学一下String源码中的写法,可能能改善一下这个问题。

/**
* Created by chenqimiao on 16/11/29.
*/
public class Main { public char[] chars = new char[10]; public void test() {
char[] chars = this.chars;
System.out.println(chars[0]);
System.out.println(chars[1]);
System.out.println(chars[2]);
} public static void main(String args[]) {
Main m = new Main();
m.test();
}
}

  

  执行 javap -c Main ,分析一下字节码,查看一下分析后生成的操作码:

String源码中的"avoid getfield opcode"

好了,我把实例变量的数组的引用赋给了一个局部引用了。

我们还是分析一下test方法中的code把

0.将第一个引用类型本地变量推送至栈顶(就是将局部变量引用chars放到栈顶)

1.获取实例域的引用推到栈顶(实例变量的成员chars放到栈顶)

4.将栈顶顶引用类型数值存入指定本地变量(就是把成员变量chars的引用给到局部变量的引用chars,这里是引用值拷贝,不是引用指向内存的值的拷贝)

5.获取指定的实例域,并将其值压入栈顶

8.将第二个引用类型本地变量推送至栈顶

9.将int型0推到栈顶

10.将char数组指定的索引的值压入到栈顶

11.调用实例方法(输出)

在14-20的过程中没有再发生"getfield"操作,而是用aload_1操作码将第二个本地引用(被赋值后的本地引用chars)推至栈顶,就可以执行接下来的一系列操作。

到这里的话"avoid getfield opcode"的意思已经非常清楚明了,在遍历实例的char数组的时候,将实例数组的引用赋值给一个本地引用,不需要频繁调用操作用码"getfield",只需要在第一次对本地引用赋值的时候,调用一次getfield,接下来的遍历取值的时候,只需要将本地引用压入到栈顶。