首先,补充javap指令详解
编译阶段Java就可以决定方法里本地变量个数,在方法调用的时候,就可以直接分配一个本地变量的区域,这个区域基于slot来分配,每个slot为4字节,任何一个数据至少占一个slot。
1、代码部分
public class test{
public static void main(String[] args) {
int a = 1, b = 1;
a = a++;
b = ++b;
System.out.println(a);
System.out.println(b);
}
}
本地javac编译成class文件后,通过javap -verbose test //输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本
2、解析输出内容
C:\Users\fedomn>javap -verbose test
Compiled from "test.java"
public class test extends java.lang.Object
SourceFile: "test.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #5.#14; // java/lang/Object."<init>":()V
const #2 = Field #15.#16; // java/lang/System.out:Ljava/io/PrintS
tream;
const #3 = Method #17.#18; // java/io/PrintStream.println:(I)V
const #4 = class #19; // test
const #5 = class #20; // java/lang/Object
const #6 = Asciz <init>;
const #7 = Asciz ()V;
const #8 = Asciz Code;
const #9 = Asciz LineNumberTable;
const #10 = Asciz main;
const #11 = Asciz ([Ljava/lang/String;)V;
const #12 = Asciz SourceFile;
const #13 = Asciz test.java;
const #14 = NameAndType #6:#7;// "<init>":()V
const #15 = class #21; // java/lang/System
const #16 = NameAndType #22:#23;// out:Ljava/io/PrintStream;
const #17 = class #24; // java/io/PrintStream
const #18 = NameAndType #25:#26;// println:(I)V
const #19 = Asciz test;
const #20 = Asciz java/lang/Object;
const #21 = Asciz java/lang/System;
const #22 = Asciz out;
const #23 = Asciz Ljava/io/PrintStream;;
const #24 = Asciz java/io/PrintStream;
const #25 = Asciz println;
const #26 = Asciz (I)V;
{
public test();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=3, Args_size=1
0: iconst_1
1: istore_1
2: iconst_1
3: istore_2
4: iload_1
5: iinc 1, 1
8: istore_1
9: iinc 2, 1
12: iload_2
13: istore_2
14: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
17: iload_1
18: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
21: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_2
25: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
28: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
line 6: 14
line 7: 21
line 8: 28
其中:Stack:表示栈顶大小(单位为一个slot)。当使用一个数据时,它首先会被放入栈顶,使用完会写回到本地变量中。
Locals:本地变量的slot个数。Args_size:人口参数个数。
minor version: 0 major version: 50表示jdk版本1.6
Constant pool:常量池。程序中需要用到常量时,会写入const后对应入口地址。
这里主要解释main函数里执行过程
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=3, Args_size=1
0: iconst_1 (把int类型常量1放到栈顶)
1: istore_1 (把栈顶的值放到第一个slot所在的本地变量a中)
2: iconst_1 (把int类型常量1放到栈顶)
3: istore_2 (把栈顶的值放到第二个slot所在的本地变量b中)
4: iload_1 (将第一个slot所在的本地变量a放入栈顶)
5: iinc 1, 1 (将第一个slot所在的本地变量a自加1)
8: istore_1 (将栈顶数据抛出写入到第一个slot所在的本地变量a中,注意:抛出的a是没有自加的,iinc只是对slot区域的a加1)
9: iinc 2, 1 (将第二个slot所在的本地变量b自加1)
12: iload_2 (将第二个slot所在的本地变量b放入栈顶)
13: istore_2 (将栈顶数据抛出写入到第二个slot所在的本地变量b中,注意:此时抛出的b是自加后的)
14: getstatic #2; (将System类的out静态属性取出来放入栈顶) //Field java/lang/System.out:Ljava/io/PrintStream;
17: iload_1 (将第一个slot所在的本地变量a放入栈顶)
18: invokevirtual #3; (调用当前类里常量池索引为3的方法 即println) //Method java/io/PrintStream.println:(I)V
21: getstatic #2; (将System类的out静态属性取出来放入栈顶) //Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_2 (将第二个slot所在的本地变量b放入栈顶)
25: invokevirtual #3; (调用当前类里常量池索引为3的方法 即println) //Method java/io/PrintStream.println:(I)V
28: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
line 6: 14
line 7: 21
line 8: 28
通过上面注释看出,Java在执行”=“操作的时候,先会把右边的值推到栈顶,然后把栈顶的值抛出赋给右边所在slot区域的本地变量中。
例1:a = 1;
第一步:将1推入栈顶
第二步:将栈顶数据抛出赋给slot区中本地变量a
例2:a = a++;
第一步:将slot区域本地变量a推入栈顶
第二步:执行iinc将slot区本地变量a+1
第三步:将栈顶数据抛出赋给slot区中本地变量a
例3:a = ++a
第一步:执行iinc将slot区域本地变量a+1
第二步:将slot区域本地变量a推入栈顶
第三步:将栈顶数据抛出赋给slot区中本地变量a
3、练习
int a = 1;
a = (a++)+(++a)+(a++);
我们也通过javap -verbose 来看执行顺序。
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=2, Args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: iinc 1, 1
6: iinc 1, 1
9: iload_1
10: iadd
11: iload_1
12: iinc 1, 1
15: iadd
16: istore_1
17: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_1
21: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
24: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 17
line 6: 24
其中iadd解释:add top two elements of stack, leave result on stack
翻译过来:
第一步:将slot区域本地变量a推入栈顶
第二步:将本地slot区域本地变量a加1
第三步:将本地slot区域本地变量a加1
第四步:将slot区域本地变量a推入栈顶
第五步:执行iadd将栈中2个int类型相加,结果放入栈中。
第六步:将slot区域本地变量a推入栈顶
第七步:将本地slot区域本地变量a加1
第八步:执行iadd将栈中2个int类型相加,结果放入栈中。
第九步:将栈顶数据抛出赋给slot区域本地变量a
最终结果为:7
过程有点繁琐,可以通过画出栈区和本地变量区解释。
4、总结
Java里一些语句执行问题,集合效率问题。我们可以通过查看其字节码,查看底层源代码来学习。