源码:
public class Test {
public static void main(String[] args) {
add(5,8,null);
}
public static long add(int a, long b,String[] args) {
long c=a+b;
long d=c+9;
return d;
}
}
字节码:
Classfile /D:/study/test/Test.class
Last modified 2018-9-25; size 584 bytes
MD5 checksum a5fc1b1ad627c6f12a1e0fc39589bd4e
Compiled from "Test.java"
public class test.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#30 // java/lang/Object."<init>":()V
#2 = Long 8l
#4 = Methodref #7.#31 // test/Test.add:(IJ[Ljava/lang/String;)J
#5 = Long 9l
#7 = Class #32 // test/Test
#8 = Class #33 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Ltest/Test;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 add
#21 = Utf8 (IJ[Ljava/lang/String;)J
#22 = Utf8 a
#23 = Utf8 I
#24 = Utf8 b
#25 = Utf8 J
#26 = Utf8 c
#27 = Utf8 d
#28 = Utf8 SourceFile
#29 = Utf8 Test.java
#30 = NameAndType #9:#10 // "<init>":()V
#31 = NameAndType #20:#21 // add:(IJ[Ljava/lang/String;)J
#32 = Utf8 test/Test
#33 = Utf8 java/lang/Object
{
public test.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/Test;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: iconst_5
1: ldc2_w #2 // long 8l
4: aconst_null
5: invokestatic #4 // Method add:(IJ[Ljava/lang/String;)J
8: pop2
9: return
LineNumberTable:
line 5: 0
line 6: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
public static long add(int, long, java.lang.String[]);
descriptor: (IJ[Ljava/lang/String;)J
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=8, args_size=3
0: iload_0
1: i2l
2: lload_1
3: ladd
4: lstore 4
6: lload 4
8: ldc2_w #5 // long 9l
11: ladd
12: lstore 6
14: lload 6
16: lreturn
LineNumberTable:
line 9: 0
line 10: 6
line 11: 14
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 a I
0 17 1 b J
0 17 3 args [Ljava/lang/String;
6 11 4 c J
14 3 6 d J
}
SourceFile: "Test.java"
add方法最大栈深度为4个槽(执行a+b时,a和b都占2个槽位,所以是4),局部变量表共8(b,c,d各占2个,a和args占一个)个槽,参数数量3个.
jvm在计算完Java方法除入参以外参数所需分配的堆栈空间,然后执行空间分配。
从call_stub例程进入entry_point例程时:
1.暂存返回地址(保证Java方法的入参(在call_stub栈顶)和Java方法的局部变量表连成一片。)
2.获取Java函数第一个入参在堆栈中的位置(当前栈顶上移n-1个指针宽度。计算入参时,默认全部当指针类型处理)
3.为局部变量分配空间(如果除了参数外,没有其它局部变量,则跳过这一步)
4.构建栈帧的一部分fixed frame,固定帧。
栈帧是个容器,存放的是函数内部的局部变量。多个栈帧连起来,就是堆栈(堆不是指堆区,只是描述多个站的关系)。
JVM为每个线程分配了一块独立的内存区域,作为线程的堆栈。64位默认每个线程堆栈空间大小为1M。1M(可以支持至少一万多个函数顺序调用,或者递归调用)一般算大的,降低堆栈大小,可以提高最大线程数。
JVM的栈帧
栈帧由:局部变量表、固定帧和操作数栈组成。
在编译成字节码时,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定,并保存到字节码文件中的方法表的Code属性中。
每一个栈帧都包含一个指向运行时常量池中改栈帧所属方法的引用,持有这个引用
是为了支持方法调用过程中的动态链接。class文件的常量池中有大量的符号引用,
字节码的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用
一部分会在类加载阶段或第一次使用时化为直接引用,这种转化称为静态解析。
另一部分在每一次运行期间转化为直接引用,这部分叫做动态链接。
所以动态链接的关键是Java方法栈中持有指向常量池的指针。
创建栈帧
1、恢复return address
return address是调用方的下一条指令的内存地址,恢复return address就是把寄存器eax(保存的就是return address)中的地址存放到栈中。
return address是紧跟在局部变量表之后的,也是固定帧的第一个元素。
2、开辟新的栈帧
此时,会把调用方的栈底保存。
3、保存最后一个入参地址
用于堆栈回收
4、计算Java方法第一个字节码的地址
5、将methodOop压栈(用于方法调用过程中读取到方法的全部信息)
6、将常量池缓存ConstanPoolCache压栈
常量池缓存中的内容都是直接引用
7、Java方法第一个入参的地址压栈(在edi中,也是指向局部变量表的指针)
8、将第四步计算的字节码地址压栈
9、将当前栈顶地址压栈(也就是操作数栈的栈底)
局部变量表
局部变量表每个变量所占的槽位是以slot为单位,而slot的长度为一个引用类型的长度(4字节或者8字节)。
除了long和double类型占用2个slot以外,其它类型都占用1个slot。
也就是说,在64位机器上,long和double在局部变量表中占用16个字节。
由下面的表可以看出,b,c,d都是占用2个槽位的。
1+2+1+2+2=8,locals=8;
args_size=3,表示有3个入参,不是指入参占用3个槽位。
public static long add(int a, long b,String[] args) {
long c=a+b;
long d=c+9;
return d;
}
stack=4, locals=8, args_size=3
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 a I
0 17 1 b J
0 17 3 args [Ljava/lang/String;
6 11 4 c J
14 3 6 d J
待续,。。