JVM内存模型
虚拟机栈
每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作数栈、返回地址等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
执行javap -c *.class 获得汇编命令
Compiled from "Hello.java"
public classcom.sun.test.Hello {publiccom.sun.test.Hello();
Code:0: aload_01: invokespecial #1 //Method java/lang/Object."":()V
4: aload_05: iconst_06: putfield #2 //Field i:I
9: return
public voidmethod1();
Code:0: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 //String ssss
5: invokevirtual #5 //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static voidmain(java.lang.String[]);
Code:0: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;
3: invokevirtual #6 //Method java/io/PrintStream.println:()V
6: return}
汇编构成
指令
操作数栈
局部变量区
构成解释
指令
不必多说顾名思义就是执行每一条的指令。
操作数栈
即每一个方法锁执行的内容了。也就是方法体中的一顿操作。
局部变量表
维护每一个操作数栈的变量表,因为要从局部变量表中拿到数据并进行入栈操作到最终弹出栈帧。
其中指令主要可以分为如下几类:
存储和加载类指令:主要包括load系列指令、store系列指令和ldc、push系列指令,主要用于在局部变量表、操作数栈和常量池三者之间进行数据调度;(关于常量池前面没有特别讲解,这个也很简单,顾名思义,就是这个池子里放着各种常量,好比片场的道具库)
对象操作指令(创建与读写访问):putfield和getfield就属于读写访问的指令,此外还有putstatic/getstatic,还有new系列指令,以及instanceof等指令。
操作数栈管理指令:如pop和dup,他们只对操作数栈进行操作。
类型转换指令和运算指令:如add/div/l2i等系列指令,实际上这类指令一般也只对操作数栈进行操作。
控制跳转指令:这类里包含常用的if系列指令以及goto类指令。
方法调用和返回指令:主要包括invoke系列指令和return系列指令。这类指令也意味这一个方法空间的开辟和结束,即invoke会唤醒一个新的java方法(新的栈和局部变量表),而return则意味着这个空间的结束回收。
栈操作相关
load和store
load 命令:用于将局部变量表的指定位置的相应类型变量加载到栈顶;
store命令:用于将栈顶的相应类型数据保入局部变量表的指定位置;
变量进栈含义变量保存含义
iload
第1个int型变量进栈
istore
栈顶int数值存入第1局部变量
iload_0
第1个int型变量进栈
istore_0
栈顶int数值存入第1局部变量
iload_1
第2个int型变量进栈
istore_1
栈顶int数值存入第2局部变量
iload_2
第3个int型变量进栈
istore_2
栈顶int数值存入第3局部变量
iload_3
第4个int型变量进栈
istore_3
栈顶int数值存入第4局部变量
-
-
-
-
lload
第1个long型变量进栈
lstore
栈顶long数值存入第1局部变量
fload
第1个float型变量进栈
fstore
栈顶float数值存入第1局部变量
dload
第1个double型变量进栈
dstore
栈顶double数值存入第1局部变量
aload
第1个ref型变量进栈
astore
栈顶ref对象存入第1局部变量
const、push和ldc
const、push:将相应类型的常量放入栈顶
ldc:则是从常量池中将常量
常量进栈含义
aconst_null
null进栈
iconst_m1
int型常量-1进栈
iconst_0
int型常量0进栈
iconst_1
int型常量1进栈
iconst_2
int型常量2进栈
iconst_3
int型常量3进栈
iconst_4
int型常量4进栈
iconst_5
int型常量5进栈
-
-
lconst_0
long型常量0进栈
fconst_0
float型常量0进栈
dconst_0
double型常量0进栈
-
-
bipush
byte型常量进栈
sipush
short型常量进栈
常量池操作含义
ldc
int、float或String型常量从常量池推送至栈顶
ldc_w
int、float或String型常量从常量池推送至栈顶(宽索引)
ldc2_w
long或double型常量从常量池推送至栈顶(宽索引)
pop和dup
pop用于栈顶数值出栈操作;
dup用于赋值栈顶的指定个数的数值,并将其压入栈顶指定次数;
栈顶操作含义
pop
栈顶数值出栈(不能是long/double)
pop2
栈顶数值出栈(long/double型1个,其他2个)
-
-
dup
复制栈顶数值,并压入栈顶
dup_x1
复制栈顶数值,并压入栈顶2次
dup_x2
复制栈顶数值,并压入栈顶3次
dup2
复制栈顶2个数值,并压入栈顶
dup2_x1
复制栈顶2个数值,并压入栈顶2次
dup2_x2
复制栈顶2个数值,并压入栈顶3次
-
-
swap
栈顶的两个数值互换,且不能是long/double
注意:dup2对于long、double类型的数据就是一个,对于其他类型的数据,才是真正的两个,这个的2代表的是2个slot的数据。
对象相关
字段调用
字段调用含义
getstatic
获取类的静态字段,将其值压入栈顶
putstatic
给类的静态字段赋值
getfield
获取对象的字段,将其值压入栈顶
putfield
给对象的字段赋值
方法调用
方法调用作用解释
invokevirtual
调用实例方法
虚方法分派
invokestatic
调用类方法
static方法
invokeinterface
调用接口方法
运行时搜索合适方法调用
invokespecial
调用特殊实例方法
包括实例初始化方法、父类方法
invokedynamic
由用户引导方法决定
运行时动态解析出调用点限定符所引用方法
方法返回
方法返回含义
ireturn
当前方法返回int
lreturn
当前方法返回long
freturn
当前方法返回float
dreturn
当前方法返回double
areturn
当前方法返回ref
对象和数组
创建类实例: new
创建数组:newarray、anewarray、multianewarray
数组元素 加载到 操作数栈:xaload (x可为b,c,s,i,l,f,d,a)
操作数栈的值 存储到数组元素: xastore (x可为b,c,s,i,l,f,d,a)
数组长度:arraylength
类实例类型:instanceof、checkcast
运算指令
运算指令是用于对操作数栈上的两个数值进行某种运算,并把结果重新存入到操作栈顶。Java虚拟机只支持整型和浮点型两类数据的运算指令,所有指令如下:
运算intlongfloatdouble
加法
iadd
ladd
fadd
dadd
减法
isub
lsub
fsub
dsub
乘法
imul
lmul
fmul
dmul
除法
idiv
ldiv
fdiv
ddiv
求余
irem
lrem
frem
drem
取反
ineg
lneg
fneg
dneg
其他运算:
位移:ishl,ishr,iushr,lshl,lshr,lushr
按位或: ior,lor
按位与: iand, land
按位异或: ixor, lxor
自增:iin
比较:dcmpg,dcmpl,fcmpg,fcmpl,lcmp
类型转换
类型转换用于将两种不同类型的数值进行转换。
(1) 对于宽化类型转换(小范围向大范围转换),无需显式的转换指令,并且是安全的操作。各种范围从小到大依次排序: int, long, float, double。
(2)对于窄化类型转换,必须显式地调用类型转换指令,并且该过程很可能导致精度丢失。转换规则中需要特别注意的是当浮点值为NaN, 则转换结果为int或long的0。虽然窄化运算可能会发生上/下限溢出和精度丢失等情况,但虚拟机规范明确规定窄化转换U不可能导致虚拟机抛出异常。
类型转换指令:i2b, i2c,f2i等等。
流程控制
控制指令是指有条件或无条件地修改PC寄存器的值,从而达到控制流程的目标
条件分支:ifeq、iflt、ifnull、ifnonnull等
复合分支:tableswitch、lookupswitch
无条件分支:goto、goto_w、jsr、jsr_w、ret
同步与异常
异常:
Java程序显式抛出异常: athrow指令。在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现,而是采用异常表来完成。
同步:
方法级的同步和方法内部分代码的同步,都是依靠管程(Monitor)来实现的。
Java语言使用synchronized语句块,那么Java虚拟机的指令集中通过monitorenter和monitorexit两条指令来完成synchronized的功能。为了保证monitorenter和monitorexit指令一定能成对的调用(不管方法正常结束还是异常结束),编译器会自动生成一个异常处理器,该异常处理器的主要目的是用于执行monitorexit指令。即monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;