1.javap反汇编工具
javap是jdk自带的反汇编工具,可以通过该命令查看编译后的class文件的常量池、字段以及方法等信息。
对于下面的这个类:
public class CalcCirc {
public int doCalc() {
int a=10;
int b=20;
int c=2;
return (a+b)*c;
}
}
使用javap -v CalcCirc.class可以看到该类的反汇编信息如下:
public int doCalc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iconst_2
7: istore_3
8: iload_1
9: iload_2
10: iadd
11: iload_3
12: imul
13: ireturn
.....//这里省去了LineNumberTable以及LocalVariableTable
stack=2表示在操作数栈的深度为2,这个一般是在编译的时候确定的,与方法中单一语句的执行复杂度有关,如果这条语句比较复杂,那么statck的数值就会增大。
locals=4表示有4个局部变量,源代码定义了a、b、c三个,再加上this变量,一共是4个局部变量。
args_size=1表示参数的个数,默认是1个参数,如果在方法中增加其他的参数则该数量会增加。
2.简单的字节码执行过程
字节码的执行过程需要关注的有以下内容:程序计数器、局部变量表、操作数栈。程序计数器用来指定程序当前运行的指针,也就是字节码的偏移量,对应的是字节码操作符前面的数字。局部变量表用来保存局部变量的最终结果,这里有4个局部变量,那么局部变量表的大小为4。操作数栈是保存执行过程中的实际内容,可以是局部变量的值,也可以是对象的引用。
执行第一条语句:0: bipush 10,程序计数器指向偏移量0,局部变量表中有this变量,将数字10压入操作数栈,执行后的结果如下:
程序计数器0局部变量表0this123操作数栈10
执行第二条语句:2: istore_1,将栈顶中的数值赋值给变量1:
程序计数器2局部变量表0this11023操作数栈
执行第三条语句:3: bipush 20,将数值20压入栈中:
程序计数器3局部变量表0this11023操作数栈20
执行第四条语句:5: istore_2,将栈顶中元素赋值给局部变量2:
程序计数器5局部变量表0this1102203操作数栈
执行第五条语句:6: iconst_2,将常量2压入栈中, 当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令 :
程序计数器6局部变量表0this1102203操作数栈2
执行第六条语句:7: istore_3,将栈顶中元素赋值给局部变量3:
程序计数器5局部变量表0this11022032操作数栈
后面的语句依次是加载局部变量1和局部变量2,iadd是从栈顶中取两个元素做加法,然后将结果放入栈中,然后加载局部变量3,imul从栈中获取两个元素做乘法,并将结果返回栈中,ireturn是将栈顶元素返回,这个返回值作为函数的返回值。
3.常用的字节码
字节码指令为一个byte整数,不同的整数代表不同的执行命令,常用的字节码指令如下:
常量入栈:
局部变量入栈: xload_n(n为0 1 2 3):x分别表示int,long,float,double,object ref,n表示局部变量下表 xaload(x为i l f d a b c s):x分别表示int, long, float, double, obj ref ,byte,char,short,该指令将从数组中取得给定索引的值,将该值压栈。比如iaload,执行前,栈:..., arrayref, index,它取得arrayref所在数组的index的值,并将值压栈。执行后,栈:..., value
出栈装载入局部变量: xstore_n(x为i l f d a,n为 0 1 2 3):将栈顶元素出栈,存入第n个局部变量 xastore(x为i l f d a b c s):将值存入数组中,iastore 执行前,栈:...,arrayref, index, value 执行后,栈:... 将value存入arrayref[index]
通用操作:nop, pop:弹出栈顶元素,dup:复制栈顶元素,复制的内容压入栈中
类型转化:i2l,i2f,d2l:将int转化为long,int转化为float,double转化为long
整数运算:iadd:int相加
浮点运算:fadd:float相加
对象操作:new:新建一个对象,getfield:获取一个对象字段,getstatic:获取静态变量
条件控制:ifeq:如果是0,则跳转,比较栈顶元素是否为0,如果是0则跳转到指定语句。
方法调用:
invokevirtual:调用实例方法,它是动态分配的调用指令,引用的类型并不能决定方法属于哪个类型,需要在实际运行的时候才能确定。 invokespecial: 调用实例初始化,父类初始化和私有方法。 invokestatic: 调用一个类(static)方法。 invokeinterface:调用类的接口方法 xreturn(x为 i l f d a 或为空):返回值,a为返回一个对象的引用
4.使用ASM生成Java字节码
ASM是java字节码操作框架,可以用于修改现有类或者动态生成新类,比如spring,AspectJ等都使用了asm框架。cglib对ASM做了一层封装,其他的框架会使用cglib来对类进行操作。
5.JIT及其相关的参数
字节码执行性能较差,对于热点代码编译成机器码再执行,在运行时的编译,叫做JIT:Just-In-Time。
所谓的热点代码是只调用的方法,或者被多次调用的循环体。jvm里会有方法调用计数器(方法调用次数)和回边计数器(方法内循环次数),通过判断这些计数器的数值是否超过阈值来判断是否将热点代码编译成机器码来执行。
-XX:CompileThreshold=1000:设置计数器的阈值 -XX:+PrintCompilation:打印出已经编译成机器码的方法