JAVASTRACE
Intro(JavaStrace) #
一个java程序的运行全过程解析。
右上java源代码,右下常量池,左下为Code方法块。
虚拟机栈 #
JVM中通过java栈,保存方法调用运行的相关信息,每当调用一个方法,会根据该方法的在字节码中的信息为该方法创建栈帧,不同的方法,其栈帧的大小有所不同。栈帧中的内存空间还可以分为3块,分别存放不同的数据:
1).局部变量表
:存放该方法调用者所传入的参数,及在该方法的方法体中创建的局部变量。
2).操作数栈
:用于存放操作数及计算的中间结果等。
3).其他栈帧信息
:如返回地址、当前方法的引用等。
只有当前正在运行的方法的栈帧位于栈顶,当前方法返回,则当前方法对应的栈帧出栈,当前方法的调用者的栈帧变为栈顶;当前方法的方法体中若是调用了其他方法,则为被调用的方法创建栈帧,并将其压入栈顶。
注意:局部变量表及操作数栈的最大深度在编译期间就已经确定了,存储在该方法字节码的Code属性中。LocalVariableTable #
局部变量表:在编译期间可以加
-g:{none,lines,vars,source}
参数生成相关调试信息。Code块可选的属性。可以用来debug,调试器可以使用它来确定方法执行期间给定局部变量的值。对于下列LocalVariableTable属性的解释:
Start
:code块有值时候的始偏移量,Length
:code块有值时候的长度,Slot
:存放变量的槽位。
官方说有值: Each entry in the local_variable_type_table array indicates a range of code array offsets within which a local variable has a value. It also indicates the index into the local variable array of the current frame at which that local variable can be found. 有些地方说" 可见",一个意思。
比如:
1).this,a,b
变量在code块中一直(0->16)有值。因为调用栈传入slot中的。
2). 而c
变量直到在 2: istore_3 存入第三个slot中时才有值,也就是在(3->16)位置有值。
3). 同理d
变量在 5: istore 4 存入第四个slot中时才有值,也就是在(7->16)位置有值。
运行过程 #
into-main() #
main方法的运行过程
stack 指定当前方法即main()方法对应栈帧中的操作数栈的最大深度,当前值为5
locals 指定main()方法中局部变量表的大小,当前为2,及有两个slot用于存放方法的参数及局部变量。
code length 指定main()方法中代码的长度,当前为31。new #7
指令:在java堆中创建一个Student对象,并将其引用值放入栈顶。dup
:复制栈顶的值,然后将复制的结果入栈。bipush 23
:将单字节常量值23入栈。ldc #8
:将#8这个常量池中的常量即”dqrcsc”取出,并入栈。ldc #9
:将#9这个常量池中的常量即”20150723”取出,并入栈。into-Student.construct() #
invokespecial #10
:调用#10这个常量所代表的方法,即Student.<init>()这个方法<init>()方法,是编译器将调用父类的<init>()的语句、构造代码块、实例字段赋值语句,以及自己编写的构造方法中的语句整合在一起生成的一个方法。保证调用父类的<init>()方法在最开头,自己编写的构造方法语句在最后,而构造代码块及实例字段赋值语句按出现的顺序按序整合到<init>()方法中。
注意到Student.<init>()方法的最大操作数栈深度为3,局部变量表大小为4。 从dup到ldc #9这四条指令向栈中添加了4个数据,虽然定义中只显式地定义了传入3个参数,而实际上会隐含传入一个当前对象的引用作为第一个参数,所以四个参数依次为this,age,name,sid。参数传递
后调用了Student.<init>()方法,会创建该方法的栈帧,并入栈。栈帧中的局部变量表的第0到4个slot分别保存着入栈的那四个参数值。aload_0
:将局部变量表slot0处的引用值入栈iload_1
:将局部变量表slot1处的int值入栈aload_2
:将局部变量表slot2处的引用值入栈invokespecial #1
:调用Person.<init>()方法,同调用Student.<init>过程类似,创建栈帧,将三个参数的值存放到局部变量表等,从Person.()返回之后,用于传参的栈顶的3个值被回收了。 aload_0
:将slot0处的引用值入栈。aload_3
:将slot3处的引用值入栈。putfield #2
:将当前栈顶的值”20150723”赋值给0x2222所引用对象的sid字段,然后栈中的两个值出栈。return
:返回调用方,即main()方法,当前方法栈帧出栈。重新回到main()方法中,继续执行下面的字节码指令:
astore_1
:将当前栈顶引用类型的值赋值给slot1处的局部变量,然后出栈。aload_1
:slot1处的引用类型的值入栈。iconst_5
:将常数5入栈,int型常数只有0-5有对应的iconst_x指令。bipush 6
:将常数6入栈。into-s.study() #
invokevirtual #11
:调用虚方法study(),这个方法是重写的接口中的方法,需要动态分派,所以使用了invokevirtual指令。
最大栈深度3,局部变量表5。bipush 10
:将10入栈istore_3
:将栈顶的10赋值给slot3处的int局部变量,即c,出栈。bipush 20
:将20入栈istore 4
:将栈顶的20付给slot4处的int局部变量,即d,出栈。
上面4条指令,完成对c和d的赋值工作。iload_1、iload_2、iload_3
这三条指令将slot1、slot2、slot3这三个局部变量入栈:imul
:将栈顶的两个值出栈,相乘的结果入栈:iadd
:将当前栈顶的两个值出栈,相加的结果入栈iload 4
:将slot4处的int型的局部变量入isub
:将栈顶两个值出栈,相减结果入栈:ireturn
:将当前栈顶的值返回到调用方。into-Student.getCnt()-and-s.run() #
重新回到main()方法中:
pop
指令,将study()方法的返回值出栈invokestatic #12
调用静态方法getCnt()不需要传任何参数pop
:getCnt()方法有返回值,将其出栈aload_1
:将slot1处的引用值入栈invokevirtual #13
:调用0x2222对象的run()方法,重写自父类的方法,需要动态分派,所以使用invokevirtual指令return
:main()返回,程序运行结束。
Reference #