java方法编译后会生成字节码指令,在运行期字节码指令会被加载到JVM内存中,使用HSDB可以查看运行期的字节码指令
贴代码:
public class Test extends BaseClass {
private Integer i=3;
private static int a=90;
{
int d=34;
}
static {
int a=1;
int b=2;
int c=a+b;
}
public Test(){
short s=8;
}
public void add(int a,int b){
Test test=this;
int z=a+b;
int x=3;
}
public static void main(String[] args){
Test test=new Test();
test.add(2,3);
}
}
javac Test.java
输出字节码
public class Test extends BaseClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
....省略了其他方法及常量池
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 // Field i:Ljava/lang/Integer;
12: bipush 34
14: istore_1
15: bipush 8
17: istore_1
18: return
public void add(int, int);
descriptor: (II)V
flags: ACC_PUBLIC
Code:
stack=2, locals=6, args_size=3 //本地变量表为6个长度,内容:this->0,a->1,b->2,test->3,z->4,x->5
0: aload_0 //加载本地变量表slot0,入栈
1: astore_3 //赋值给test
2: iload_1 //加载本地变量表slot1,入栈
3: iload_2 //加载本地变量表slot2,入栈
4: iadd //执行iadd操作后,入栈
5: istore 4 //弹栈,入本地变量表slot4
7: iconst_3 //将3入栈
8: istore 5 //弹栈,入本地变量表slot5
10: return
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=3, args_size=0
0: bipush 90
2: putstatic #7 // Field a:I
5: iconst_1
6: istore_0
7: iconst_2
8: istore_1
9: iload_0
10: iload_1
11: iadd
12: istore_2
13: return
}
jdb命令打断点调试
> jdb -XX:+UseSerialGC -Xmx10m -XX:-UseCompressedOops
> stop in Test.add
> run Test
开启HSDB
> echo $JAVA_HOME
> cd %JAVA_HOME%\lib
> sudo java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
jps 查看运行Test java的进程号 我这里拿到是4801
打开Tools->class Browser
点开add方法 可以得知该方法的签名与地址 0x000000010f6002f8
对比javap 展开add方法的字节码,是完全一样的
字节码指令被分配在constMethodOop对象的内存区域的末尾
接下来,分析add方法,在jvm内部所对应的methodOpp对象实例的内存结构及各个字段的内存地址
可以看出constMethodOop字段的内存址 0x000000010f6002b0,这个地址就是Test.add()方法在JVM内部所对应的constMethodOop对象实例的内存地址,而add方法的字节码指令就在constMethodOop对象实例的内存后面
对比这张表,算出ConstMethodOop占用的内存空间,共有44字节
根据内存对齐规则:
整个类型实例所占空间必须是类型中宽度最大的字段所占内存的整数位
constMethodOop包含指针,如_constants是8个字节,那最终是48个字节
因此add()方法的字节码指令的开始地址:0x000000010f6002b0+0x30 = 0x000000010f6002e0
采用HSDB的mem命令
加上选项2,表示连续输出两行
hsdb> mem 0x000000010f6002e0 2
0x000000010f6002e0: 0x060436601c1b4eca
0x000000010f6002e8: 0x29112600ffb10536
jvm中每个字节码指令都占1字节长度
按照字节码,在内存中分配的顺序,从低到高,即java方法的第一条字节码在低位,java方法的最后一条指令在高位,所以要进行逆转
0x000000010f6002e0: ca 4e 1b 1c 60 36 04 06
0x000000010f6002e8: 36 05 b1 ff 00 26 11 29
对照字节码转16进制
ca 4e 1b 1c 60 36 04 06 36 05 b1 ff 00 26 11 29
astore_3 iload_1 iload_2 iadd istore slot4 iconst_3 istore slot5 return
ca地址不对,是因为这个地址被打了断点
对照一下,是不是一样的
public void add(int, int);
descriptor: (II)V
flags: ACC_PUBLIC
Code:
stack=2, locals=6, args_size=3 //本地变量表为6个长度,内容:this->0,a->1,b->2,test->3,z->4,x->5
0: aload_0 //加载本地变量表slot0,入栈
1: astore_3 //赋值给test
2: iload_1 //加载本地变量表slot1,入栈
3: iload_2 //加载本地变量表slot2,入栈
4: iadd //执行iadd操作后,入栈
5: istore 4 //弹栈,入本地变量表slot4
7: iconst_3 //将3入栈
8: istore 5 //弹栈,入本地变量表slot5
10: return
另一种方式,那就是直接查看主线程的堆栈结构
很明显,断点处就是方法内存开始处
好了,最后推荐一个好用的vpn 加速度