jvm虚拟机规范 紧接上文的
dneg
dneg指令是类型安全的,if在传入的操作数堆栈中有一个类型匹配的双倍数
。dneg指令不改变类型状态。
instructionIsTypeSafe(dneg, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [double], double,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
drem
if等同的dadd指令是类型安全的,那么drem指令就是类型安全的。
instructionHasEquivalentTypeRule(drem, dadd)。
dreturn
if包围方法的返回类型是double,那么
dreturn指令是类型安全的,并且可以有效地从传入的操作数栈中弹出一个类型匹配的double
。
instructionIsTypeSafe(dreturn, Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :- thisMethodReturnType(Environment, double)。canPop(StackFrame, [double], _PoppedStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
dstore, dstore_
if一条具有操作数Index的
存储指令和类型为double的
存储指令是类型安全的,并产生一个流出的类型状态NextStackFrame
。
instructionIsTypeSafe(dstore(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- storeIsTypeSafe(Environment, Index, double, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令dstore_ ,对于0≤n≤3,if等价的dstore指令是类型安全的。
instructionHasEquivalentTypeRule(dstore_0, dstore(0))。
instructionHasEquivalentTypeRule(dstore_1, dstore(1))。
instructionHasEquivalentTypeRule(dstore_2, dstore(2))。
instructionHasEquivalentTypeRule(dstore_3, dstore(3))。
dsub
if等同的dadd指令是类型安全的,那么dsub指令就是类型安全的。
instructionHasEquivalentTypeRule(dsub, dadd)。
Type
if人们可以有效地用Type
、Type
替换一个第1类
类型,产生出的类型状态,那么一个dup指令就是类型安全的。
instructionIsTypeSafe(dup, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :-StackFrame = frame(Locals, InputOperandStack, Flags)。popCategory1(InputOperandStack, Type, _)。canSafelyPush(Environment, InputOperandStack, Type, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
dup_x1
一个dup_x1指令是类型安全的,if可以有效地用Type1
、Type2
、Type1
的类型替换传入操作数堆栈中的两个第1类类型Type1
和Type2,
产生传出的类型状态。
instructionIsTypeSafe(dup_x1, Environment, _Offset, StackFrame.)NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, InputOperandStack, Flags)。popCategory1(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Rest)。canSafelyPushList(Environment, Rest, [Type1, Type2, Type1],OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
dup_x2
if一条dup_x2指令是dup_x2指令的类型安全形式,那么它就是类型安全的。
instructionIsTypeSafe(dup_x2, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, InputOperandStack, Flags)。dup_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条dup_x2指令是类型安全形式的dup_x2指令,它是类型安全形式1的dup_x2指令或类型安全形式2的dup_x2指令。
dup_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup_x2Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。dup_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup_x2Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。
一条dup_x2指令是一条类型安全的形式1 dup_x2指令,if可以有效地将传入的操作数堆栈中的三个第1类类型Type1
、Type2
、Type3
替换为Type1
、Type2、Type3
、Type1
,产生传出的类型状态。
dup_x2Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory1(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Stack2)。popCategory1(Stack2, Type3, Rest)。canSafelyPushList(Environment, Rest, [Type1, Type3, Type2, Type1],OutputOperandStack)。
一条dup_x2指令是一条类型安全的形式2 dup_x2指令,if可以有效地用Type1
、Type2
、Type1的
类型替换输入操作数堆栈中的第1类类型Type1
和第2类类型Type2
,得到输出的类型状态。
dup_x2Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory1(InputOperandStack, Type1, Stack1)。popCategory2(Stack1, Type2, Rest)。canSafelyPushList(Environment, Rest, [Type1, Type2, Type1],OutputOperandStack)。
dup2
if一条dup2指令是dup2指令的类型安全形式,那么它就是类型安全的。
instructionIsTypeSafe(dup2, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :-StackFrame = frame(Locals, InputOperandStack, Flags)。dup2FormIsTypeSafe(Environment,InputOperandStack, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条dup2指令是类型安全形式的dup2指令,它是类型安全形式1的dup2指令或类型安全形式2的dup2指令。
dup2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2Form1IsTypeSafe(Environment,InputOperandStack, OutputOperandStack)。dup2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2Form2IsTypeSafe(Environment,InputOperandStack, OutputOperandStack)。
一条dup2指令是一条类型安全的形式1 dup2指令,if可以有效地将传入的操作数堆栈中的两个类别1的类型Type1
和Type2
替换为Type1
、Type2
、Type1
、Type2
,产生传出的类型状态。
dup2Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack):-popCategory1(InputOperandStack, Type1, TempStack)。popCategory1(TempStack, Type2, _)。canSafelyPushList(Environment, InputOperandStack, [Type1, Type2],OutputOperandStack)。
一条dup2指令是一条类型安全的形式2 dup2指令,if可以有效地用Type
、Type
替换传入操作数栈上的第2类类型,产生传出的类型状态。
dup2Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack):-popCategory2(InputOperandStack, Type, _)。canSafelyPush(Environment, InputOperandStack, Type, OutputOperandStack)。
dup2_x1
if一条dup2_x1指令是dup2_x1指令的类型安全形式,那么它就是类型安全的。
instructionIsTypeSafe(dup2_x1, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, InputOperandStack, Flags)。dup2_x1FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条dup2_x1指令是类型安全形式的dup2_x1指令,那么它就是类型安全形式1的dup2_x1指令或类型安全形式2的dup_x2指令。
dup2_x1FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x1Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。dup2_x1FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x1Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。
一条dup2_x1指令是一条类型安全的形式1 dup2_x1指令,if可以有效地将传入的操作数堆栈中的三个第1类类型Type1
, Type2
, Type3
替换为Type1
, Type2
, Type3
, Type1
, Type2的类型
,得到传出的类型状态。
dup2_x1Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory1(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Stack2)。popCategory1(Stack2, Type3, Rest)。canSafelyPushList(Environment, Rest, [Type2, Type1, Type3, Type2, Type1],OutputOperandStack)。
一条dup2_x1指令是一条类型安全的形式2 dup2_x1指令,即可以有效地将传入的操作数堆栈中的第二类类型Type1
和第一类类型Type2
替换为Type1
、Type2
、Type1的
类型,产生传出类型状态。
dup2_x1Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory2(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Rest)。canSafelyPushList(Environment, Rest, [Type1, Type2, Type1],OutputOperandStack)。
dup2_x2
if一条dup2_x2指令是dup2_x2指令的类型安全形式,那么它就是类型安全的。
instructionIsTypeSafe(dup2_x2, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, InputOperandStack, Flags)。dup2_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
dup2_x2指令是dup2_x2指令的类型安全形式,if下列情况之一成立。
· 它是一个类型安全的形式1 dup2_x2指令。
· 它是一个类型安全的形式2 dup2_x2指令。
· 它是一个类型安全的形式3 dup2_x2指令。
· 它是一个类型安全的形式4 dup2_x2指令。
dup2_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x2Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。dup2_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x2Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。dup2_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x2Form3IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。dup2_x2FormIsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-dup2_x2Form4IsTypeSafe(Environment, InputOperandStack, OutputOperandStack)。
一条dup2_x2指令是一条类型安全的形式1 dup2_x2指令,if可以有效地用Type1
, Type2
, Type3
, Type4的
类型替换传入操作数栈上的Type1
, Type2
, Type3
, Type4
, Type1
, Type2的
类型,得到传出的类型状态。
dup2_x2Form1IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory1(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Stack2)。popCategory1(Stack2, Type3, Stack3)。popCategory1(Stack3, Type4, Rest)。canSafelyPushList(Environment, Rest,[类型2,类型1,类型4,类型3,类型2,类型1]。OutputOperandStack)。
一条dup2_x2指令是一条类型安全的形式2 dup2_x2指令,if可以有效地用Type1
、Type2
、Type3
、Type1
的类型替换传入操作数栈上的一个第二类类型Type1
和两个第一类类型Type2
、Type3
,产生传出的类型状态。
dup2_x2Form2IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory2(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Stack2)。popCategory1(Stack2, Type3, Rest)。canSafelyPushList(Environment, Rest,[类型1、类型3、类型2、类型1]。OutputOperandStack)。
一条dup2_x2指令是一条类型安全的形式3 dup2_x2指令,if可以有效地用Type1
, Type2, Type3
, Type1
, Type2的类型
替换传入操作数栈上的两个第一类类型,Type1, Type2
和一个第二类类型,产生传出类型状态。
dup2_x2Form3IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory1(InputOperandStack, Type1, Stack1)。popCategory1(Stack1, Type2, Stack2)。popCategory2(Stack2, Type3, Rest)。canSafelyPushList(Environment, Rest,[类型2,类型1,类型3,类型2,类型1]。OutputOperandStack)。
一条dup2_x2指令是一条类型安全的形式4 dup2_x2指令,即可以有效地将传入操作数堆栈中的两个第2类类型Type1
、Type2
替换为Type1
、Type2
、Type1
,产生传出的类型状态。
dup2_x2Form4IsTypeSafe(Environment, InputOperandStack, OutputOperandStack) :-popCategory2(InputOperandStack, Type1, Stack1)。popCategory2(Stack1, Type2, Rest)。canSafelyPushList(Environment, Rest, [Type1, Type2, Type1],OutputOperandStack)。
f2d, f2I, f2l
if可以有效地从传入的操作数堆栈中弹出float
并替换为double
,产生传出的类型状态,那么f2d指令就是类型安全的。
instructionIsTypeSafe(f2d, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float], double,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if可以有效地从传入的操作数堆栈中弹出float
并替换为int
,产生传出的类型状态,那么f2i指令就是类型安全的。
instructionIsTypeSafe(f2i, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if可以有效地从传入的操作数堆栈中弹出float
并替换为long
,产生传出的类型状态,那么f2l指令就是类型安全的。
instructionIsTypeSafe(f2l, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fadd
一个fadd指令是类型安全的,if可以有效地用float
替换传入操作数栈中与float
匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(fadd, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float, float], float,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
faload
一个faload指令是类型安全的,if人们可以有效地用float
替换传入操作数栈中与int
和数组相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(faload, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, arrayOf(float)], float,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fastore
一个fastore指令是类型安全的,if可以有效地从传入的操作数栈中弹出与float
、int
和float
的数组相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(fastore, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [float, int, arrayOf(float)], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fcmp
一个fcmpg指令是类型安全的,if可以有效地用int
替换传入操作数堆栈中与float
和float
匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(fcmpg, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float, float], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if等同的fcmpg指令是类型安全的,那么fcmpl指令就是类型安全的。
instructionHasEquivalentTypeRule(fcmpl, fcmpg)。
fconst_<f
if可以有效地将float
类型推到传入的操作数栈上,产生传出的类型状态,那么fconst_0指令就是类型安全的。
instructionIsTypeSafe(fconst_0, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [], float, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fconst的其他变体的规则是等同的。
instructionHasEquivalentTypeRule(fconst_1, fconst_0)。
instructionHasEquivalentTypeRule(fconst_2, fconst_0)。
fdiv
if等同的fadd指令是类型安全的,那么fdiv指令就是类型安全的。
instructionHasEquivalentTypeRule(fdiv, fadd)。
fload, fload_
if一条带有操作数Index的
fload指令是类型安全的,并产生出类型状态NextStackFrame
,那么带有操作数Index
和类型float的
load指令就是类型安全的,并产生出类型状态NextStackFrame
。
instructionIsTypeSafe(fload(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- loadIsTypeSafe(Environment, Index, float, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令fload_ ,对于0≤n≤3,是类型安全的,if相当于fload指令是类型安全的。
instructionHasEquivalentTypeRule(fload_0, fload(0))。
instructionHasEquivalentTypeRule(fload_1, fload(1))。
instructionHasEquivalentTypeRule(fload_2, fload(2))。
instructionHasEquivalentTypeRule(fload_3, fload(3))。
fmul
if等同的fadd指令是类型安全的,那么fmul指令就是类型安全的。
instructionHasEquivalentTypeRule(fmul, fadd)。
fneg
if在传入的操作数堆栈中存在一个类型匹配的float,则
fneg指令是类型安全的。fneg指令不改变类型状态。
instructionIsTypeSafe(fneg, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [float], float,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fadd
if等同的fadd指令是类型安全的,那么frem指令就是类型安全的。
instructionHasEquivalentTypeRule(frem, fadd)。
float
if包围方法的返回类型是float,那么
freturn指令是类型安全的,并且可以有效地从进入的操作数堆栈中弹出一个类型匹配的float
。
instructionIsTypeSafe(freturn, Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :- thisMethodReturnType(Environment, float)。canPop(StackFrame, [float], _PoppedStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
fstore, fstore_
if一条具有操作数Index的
fstore指令是类型安全的,并且产生一个流出的类型状态NextStackFrame
,那么具有操作数Index
和类型float的
存储指令是类型安全的,并且产生一个流出的类型状态NextStackFrame
。
instructionIsTypeSafe(fstore(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- storeIsTypeSafe(Environment, Index, float, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令fstore_ ,对于0≤n≤3,是类型安全的,if等同的fstore指令是类型安全的。
instructionHasEquivalentTypeRule(fstore_0, fstore(0))。
instructionHasEquivalentTypeRule(fstore_1, fstore(1))。
instructionHasEquivalentTypeRule(fstore_2, fstore(2))。
instructionHasEquivalentTypeRule(fstore_3, fstore(3))。
fsub
if等同的fadd指令是类型安全的,那么fsub指令就是类型安全的。
instructionHasEquivalentTypeRule(fsub, fadd)。
getfield
有操作数CP
的getfield指令是类型安全的,ifCP
指的是一个常量池条目,表示一个声明类型为FieldType的
字段,该字段在类FieldClass
中声明,并且可以在传入的操作数堆栈中有效地用FieldType
类型替换匹配FieldClass的类型,
产生传出的类型状态。FieldClass
不能是一个数组类型。受保护的
字段要接受额外的检查(§4.10.1.8)。
instructionIsTypeSafe(getfield(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = field(FieldClass, FieldName, FieldDescriptor)。parseFieldDescriptor(FieldDescriptor, FieldType)。passesProtectedCheck(Environment, FieldClass, FieldName,FieldDescriptor, StackFrame)。validTypeTransition(Environment, [class(FieldClass)], FieldType,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
getstatic
有操作数CP
的getstatic指令是类型安全的,ifCP
指的是一个常量池条目,表示一个声明类型为FieldType的
字段,并且可以有效地将FieldType
推到传入的操作数栈上,产生传出的类型状态。
instructionIsTypeSafe(getstatic(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = field(_FieldClass, _FieldName, FieldDescriptor)。parseFieldDescriptor(FieldDescriptor, FieldType)。validTypeTransition(Environment, [], FieldType,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
####s## goto,goto_w
if一个goto指令的目标操作数是一个有效的分支目标,那么该指令就是类型安全的。
instructionIsTypeSafe(goto(Target), Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :-targetIsTypeSafe(Environment, StackFrame, Target)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if等价的goto指令是类型安全的,那么一条goto_w指令就是类型安全的。
指令有相等的类型规则(goto_w(Target), goto(Target))。
i2b, i2c, i2d, i2f, i2l, i2s
if等同的ineg指令是类型安全的,那么i2b指令就是类型安全的。
instructionHasEquivalentTypeRule(i2b, ineg)。
if一条i2c指令是类型安全的,那么相当于ineg指令是类型安全的。
instructionHasEquivalentTypeRule(i2c, ineg)。
if一个i2d指令可以有效地从传入的操作数堆栈中弹出int
并替换为double
,产生传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(i2d, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int], double,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一个i2f指令可以有效地从传入的操作数堆栈中弹出int
并替换为float
,产生传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(i2f, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int], float,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一个i2l指令可以有效地从传入的操作数堆栈中弹出int
并替换为long
,产生传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(i2l, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
一个i2s指令是类型安全的,if等价的ineg指令是类型安全的。
instructionHasEquivalentTypeRule(i2s, ineg)。
iadd
一个iadd指令是类型安全的,if人们可以有效地用int
替换传入操作数栈上与int
匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(iadd, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, int], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
iaload
iaload指令是类型安全的,if人们可以有效地将传入的操作数栈上与int和int
的数组相匹配的类型替换为int,从而
产生传出的类型状态。
instructionIsTypeSafe(iaload, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, arrayOf(int)], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
iadd
if等同的iadd指令是类型安全的,那么iand指令就是类型安全的。
instructionHasEquivalentTypeRule(iand,iadd)。
iastore
iastore指令是类型安全的,if可以有效地从传入的操作数栈中弹出与int
、int
和int
的数组相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(iastore, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [int, int, arrayOf(int)], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if_acmp
if_acmpeq指令是类型安全的,if可以有效地在传入的操作数堆栈上弹出与引用
相匹配的类型,产生传出的类型状态NextStackFrame
,并且指令的操作数Target
是一个有效的分支目标,假设传入的类型状态为NextStackFrame
。
instructionIsTypeSafe(if_acmpeq(Target), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [ reference, reference], NextStackFrame)。targetIsTypeSafe(环境,NextStackFrame,目标)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if_acmpne的规则是相同的。
instructionHasEquivalentTypeRule(if_acmpne(Target), if_acmpeq(Target))。
if_icmp
if_icmpeq指令是类型安全的,if可以在传入的操作数堆栈上有效地弹出与int
和int
相匹配的类型,产生传出的类型状态NextStackFrame
,并且指令的操作数Target
是一个有效的分支目标,假设传入的类型状态为NextStackFrame
。
instructionIsTypeSafe(if_icmpeq(Target), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [int, int], NextStackFrame)。targetIsTypeSafe(环境,NextStackFrame,目标)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if_icmp指令的所有其他变体的规则是相同的。
instructionHasEquivalentTypeRule(if_icmpge(Target), if_icmpeq(Target))。
instructionHasEquivalentTypeRule(if_icmpgt(Target), if_icmpeq(Target))。
instructionHasEquivalentTypeRule(if_icmple(Target), if_icmpeq(Target))。
instructionHasEquivalentTypeRule(if_icmplt(Target), if_icmpeq(Target))。
instructionHasEquivalentTypeRule(if_icmpne(Target), if_icmpeq(Target))。
if
一个ifeq指令是类型安全的,if可以有效地从传入的操作数堆栈中弹出一个类型匹配的int,
产生传出的类型状态NextStackFrame
,并且指令的操作数Target
是一个有效的分支目标,假设传入的类型状态为NextStackFrame
。
instructionIsTypeSafe(ifeq(Target), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :-canPop(StackFrame, [int], NextStackFrame)。 targetIsTypeSafe(环境,NextStackFrame,目标)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
所有其他if指令的变化规则是相同的。
指令有相等的类型规则(ifge(Target), ifeq(Target))。
instructionHasEquivalentTypeRule(ifgt(Target), ifeq(Target))。
instructionHasEquivalentTypeRule(ifle(Target), ifeq(Target))。
指令有相等的类型规则(iflt(Target), ifeq(Target))。
指令有相等的类型规则(ifne(Target), ifeq(Target))。
ifnonnull
一个ifnonnull指令是类型安全的,if可以有效地从传入的操作数堆栈中弹出一个类型匹配的引用,
产生传出的类型状态NextStackFrame
,并且指令的操作数Target
是一个有效的分支目标,假设传入的类型状态为NextStackFrame
。
instructionIsTypeSafe(ifnonnull(Target), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [reference], NextStackFrame)。targetIsTypeSafe(环境,NextStackFrame,目标)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
ifnull
if一条ifnull指令是类型安全的,那么相当于ifnonnull指令是类型安全的。
instructionHasEquivalentTypeRule(ifnull(Target), ifnonnull(Target))。
iinc
第一操作数为Index的
iinc指令是类型安全的,ifLIndex
具有int
类型。iinc指令不改变类型状态。
instructionIsTypeSafe(iinc(Index, _Value), _Environment, _Offset,堆栈框架, 堆栈框架, 异常堆栈框架) :-StackFrame = frame(Locals, _OperandStack, _Flags)。nth0(Index, Locals, int)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
iload,iload
if一条带有操作数Index
的加载指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
,那么带有操作数Index
和类型int的
加载指令就是类型安全的,并且产生一个出场的类型状态NextStackFrame
。
instructionIsTypeSafe(iload(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- loadIsTypeSafe(Environment, Index, int, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令iload_ ,对于0≤n≤3,是类型安全的,if等价的iload指令是类型安全的。
instructionHasEquivalentTypeRule(iload_0, iload(0))。
指令有相等的类型规则(iload_1, iload(1))。
指令有相等的类型规则(iload_2, iload(2))。
instructionHasEquivalentTypeRule(iload_3, iload(3))。
imul
if等同的iadd指令是类型安全的,那么imul指令就是类型安全的。
instructionHasEquivalentTypeRule(imul, iadd)。
ineg
if在传入的操作数堆栈中存在一个类型匹配的int,
则ineg指令是类型安全的。ineg指令不改变类型状态。
instructionIsTypeSafe(ineg, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int], int, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
instanceof
有操作数CP
的instanceof指令是类型安全的,ifCP
指的是一个表示类或数组的常量池条目,并且我们可以有效地将传入操作数堆栈顶部的Object
类型替换为int
类型,产生传出的类型状态。
instructionIsTypeSafe(instanceof(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- (CP = class(_, _) ; CP = arrayOf(_))。isBootstrapLoader(BL)。validTypeTransition(Environment, [class('java/lang/Object', BL)], int,堆栈框架,NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
invokedynamic
一个被调用的动态指令是类型安全的,if以下所有情况都是真的。
· 它的第一个操作数,CP
,指的是一个常量池条目,表示一个动态调用站点,名称为CallSiteName
,描述符为Descriptor
。
· CallSiteName
不是<init>
。
· CallSiteName
不是<clinit>
。
· 我们可以有效地用Descriptor
中给出的返回类型替换传入操作数堆栈中与参数类型相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(invokedynamic(CP,0,0), Environment, _Offset,StackFrame, NextStackFrame, ExceptionStackFrame) :- CP = dmethod(CallSiteName, Descriptor),CallSiteName \= '<init>' 。CallSiteName \= '<clinit>' 。parseMethodDescriptor(Descriptor, OperandArgList, ReturnType)。reverse(OperandArgList, StackArgList)。validTypeTransition(Environment, StackArgList, ReturnType,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
invokeinterface
一个invokeinterface指令是类型安全的,if以下所有情况都是真的。
· 它的第一个操作数,CP
,指的是一个常量池条目,表示一个名为MethodName的
接口方法,其描述符Descriptor
是接口MethodIntfName
的成员。
· MethodName
不是<init>
。
· MethodName
不是<clinit>
。
· 它的第二个操作数,Count
,是一个有效的计数操作数(见下文)。
· 我们可以有效地用Descriptor
中给出的返回类型替换传入操作数栈中与MethodIntfName
类型和Descriptor
中给出的参数类型相匹配的类型,产生传出类型状态。
instructionIsTypeSafe(invokeinterface(CP, Count, 0), Environment, _Offset,StackFrame, NextStackFrame, ExceptionStackFrame) :- CP = imethod(MethodIntfName, MethodName, Descriptor)。MethodName \= '<init>' 。MethodName \= '<clinit>' 。parseMethodDescriptor(Descriptor, OperandArgList, ReturnType)。currentClassLoader(Environment, CurrentLoader)。reverse([class(MethodIntfName, CurrentLoader) | OperandArgList],StackArgList)。canPop(StackFrame, StackArgList, TempFrame)。validTypeTransition(Environment, [], ReturnType, TempFrame, NextStackFrame)。countIsValid(Count, StackFrame, TempFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
invokeinterface指令的Count
操作数if等于该指令的参数大小,则有效。这等于InputFrame
和OutputFrame
的大小之差。
countIsValid(Count, InputFrame, OutputFrame) :-InputFrame = frame(_Locals1, OperandStack1, _Flags1),OutputFrame = frame(_Locals2, OperandStack2, _Flags2),length(OperandStack1, Length1)。length(OperandStack2, Length2)。计数 =:= 长度1 - 长度2。
invokespecial
if以下情况都是真的,那么一条invokespecial指令是类型安全的。
· 它的第一个操作数,CP
,指的是一个常量池条目,表示一个名为MethodName的
方法,描述符为Descriptor
,是MethodClassName
类的成员。
· 要么。
o MethodName
不是<init>
。
o MethodName
不是<clinit>
。
o 我们可以有效地用Descriptor中给出
的返回类型来替换传入操作数栈中与当前类和参数类型相匹配的类型,从而产生传出类型状态。
o 人们可以有效地用Descriptor中给出
的返回类型来替换传入操作数堆栈中与MethodClassName
类相匹配的类型和Descriptor
中给出的参数类型。
instructionIsTypeSafe(invokespecial(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = method(MethodClassName, MethodName, Descriptor)。MethodName \= '<init>' 。MethodName \= '<clinit>' 。parseMethodDescriptor(Descriptor, OperandArgList, ReturnType)。thisClass(Environment, class(CurrentClassName, CurrentLoader))。 reverse([class(CurrentClassName, CurrentLoader) | OperandArgList],StackArgList)。validTypeTransition(Environment, StackArgList, ReturnType,StackFrame, NextStackFrame)。reverse([class(MethodClassName, CurrentLoader) | OperandArgList],StackArgList2)。validTypeTransition(Environment, StackArgList2, ReturnType,StackFrame, _ResultStackFrame)。isAssignable(class(CurrentClassName, CurrentLoader),class(MethodClassName, CurrentLoader))。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
· 或者。
o MethodName是<init>
。
o 描述符
指定了一个无效的
返回类型。
o 我们可以有效地从传入的操作数栈中弹出与Descriptor
中给出的参数类型相匹配的类型和一个未初始化的类型UninitializedArg,
产生OperandStack
。
o 传出的类型状态是从传入的类型状态衍生出来的,首先用OperandStack替换
传入的操作数栈,然后用被初始化的实例类型替换UninitializedArg
的所有实例。
instructionIsTypeSafe(invokespecial(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :-CP = method(MethodClassName, '<init> ', Descriptor)。parseMethodDescriptor(Descriptor, OperandArgList, void)。 reverse(OperandArgList, StackArgList)。canPop(StackFrame, StackArgList, TempFrame)。TempFrame = frame(Locals, FullOperandStack, Flags),FullOperandStack = [UninitializedArg | OperandStack]。currentClassLoader(Environment, CurrentLoader)。rewrittenUninitializedType(UninitializedArg, Environment,class(MethodClassName, CurrentLoader), This)。 rewrittenInitializationFlags(UninitializedArg, Flags, NextFlags)。 substitute(UninitializedArg, This, OperandStack, NextOperandStack)。substitute(UninitializedArg, This, Locals, NextLocals)。NextStackFrame = frame(NextLocals, NextOperandStack, NextFlags)。ExceptionStackFrame = frame(Locals, [], Flags),passesProtectedCheck(Environment, MethodClassName, '<init> ',Descriptor, NextStackFrame)。
要计算未初始化的参数的类型需要改写成什么类型,有两种情况。
· if我们在一个对象的构造函数中初始化它,它的类型最初是uninitializedThis
。这个类型将被改写为<init>
方法的类的类型。
· 第二种情况是由new创建的对象的初始化引起的。未初始化的arg类型被改写为MethodClass
,即<init>
的方法持有者的类型。我们检查地址
处是否真的有一条new指令。
rewrittenUninitializedType(uninitializedThis, Environment,MethodClass, MethodClass) :-MethodClass = class(MethodClassName, CurrentLoader),thisClass(Environment, MethodClass)。 rewrittenUninitializedType(uninitializedThis, Environment,MethodClass, MethodClass) :-MethodClass = class(MethodClassName, CurrentLoader),thisClass(Environment, class(thisClassName, thisLoader))。superclassChain(thisClassName, thisLoader, [MethodClass | Rest])。rewrittenUninitializedType(uninitialized(Address), Environment,MethodClass, MethodClass) :-allInstructions(Environment, Instructions)。成员(指令(地址,new(MethodClass)),指令)。rewrittenInitializationFlags(uninitializedThis, _Flags, [])。
rewrittenInitializationFlags(uninitialized(_), Flags, Flags)。替代(_Old, _New, [], [])。
substitute(Old, New, [Old | FromRest], [New | ToRest] ) :-代替(Old, New, FromRest, ToRest)。
substitute(Old, New, [From1 | FromRest], [From1 | ToRest] ) :-From1\= Old,代替(Old, New, FromRest, ToRest)。
<init>
方法的invokespecial规则是传回一个独立的异常堆栈帧的唯一动机。我们担心的是,当在构造函数中初始化一个对象时,invokespecial会导致超类的<init>
方法被调用,而调用可能会失败,导致这个对象
未被初始化。这种情况不能用Java编程语言的源代码来创建,但可以通过直接用字节码编程来创建。
在这种情况下,原框架在局部变量0中持有一个未初始化的对象,并且有flagThisUninit标志
。invokespecial的正常终止会初始化未初始化的对象并关闭flagThisUninit
标志。但是if调用<init>
方法抛出一个异常,未初始化的对象可能会被留在部分初始化的状态下,需要使其永久不能使用。这由一个异常帧来表示,其中包含破碎的对象(本地的新值)和flagThisUninit
标志(旧的标志)。没有办法从一个带有flagThisUninit标志的
明显初始化的对象到一个正确初始化的对象,所以这个对象是永久不可用的。
if不是这种情况,异常堆栈框架的标志将总是与输入堆栈框架的标志相同。
invokestatic
一个调用指令是类型安全的,if以下所有情况都是真的。
· 它的第一个操作数,CP
,指的是一个常量池条目,表示一个名为MethodName的
方法,描述符为Descriptor
。
· MethodName
不是<init>
。
· MethodName
不是<clinit>
。
· 我们可以有效地用Descriptor
中给出的返回类型替换传入操作数堆栈中与参数类型相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(invokestatic(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = method(_MethodClassName, MethodName, Descriptor)。MethodName \= '<init>' 。MethodName \= '<clinit>' 。parseMethodDescriptor(Descriptor, OperandArgList, ReturnType)。 reverse(OperandArgList, StackArgList)。validTypeTransition(Environment, StackArgList, ReturnType,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
invokevirtual
if以下情况都是真的,那么一条调用虚拟指令是类型安全的。
· 它的第一个操作数,CP
,指的是一个常量池条目,表示一个名为MethodName的
方法,描述符为Descriptor
,是MethodClassName
类的成员。
· MethodName
不是<init>
。
· MethodName
不是<clinit>
。
· 人们可以有效地用Descriptor
中给出的返回类型替换传入操作数堆栈中与MethodClassName
类相匹配的类型和Descriptor中给出的
参数类型,产生传出类型状态。
· if该方法是受保护的
,其用法符合对受保护
成员的访问的特殊规则(§4.10.1.8)。
instructionIsTypeSafe(invokevirtual(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = method(MethodClassName, MethodName, Descriptor)。MethodName \= '<init>' 。MethodName \= '<clinit>' 。parseMethodDescriptor(Descriptor, OperandArgList, ReturnType)。 reverse(OperandArgList, ArgList)。currentClassLoader(Environment, CurrentLoader)。reverse([class(MethodClassName, CurrentLoader) | OperandArgList],StackArgList)。validTypeTransition(Environment, StackArgList, ReturnType,StackFrame, NextStackFrame)。canPop(StackFrame, ArgList, PoppedFrame)。passesProtectedCheck(Environment, MethodClassName, MethodName.)Descriptor, PoppedFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
ior
if等同的iadd指令是类型安全的,那么ior指令就是类型安全的。
instructionHasEquivalentTypeRule(ior, iadd)。
irem
if等同的iadd指令是类型安全的,那么irem指令就是类型安全的。
instructionHasEquivalentTypeRule(irem, iadd)。
ireturn
if包围方法的返回类型是int
,并且可以有效地从传入的操作数堆栈中弹出一个类型匹配的int,那么
ireturn指令就是类型安全的。
instructionIsTypeSafe(ireturn, Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :- thisMethodReturnType(Environment, int)。canPop(StackFrame, [int], _PoppedStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
ishi, ishr, iushr
if等同的iadd指令是类型安全的,那么ishl指令就是类型安全的。
instructionHasEquivalentTypeRule(ishl, iadd)。
if等价的iadd指令是类型安全的,那么一条ishr指令就是类型安全的。
instructionHasEquivalentTypeRule(ishr, iadd)。
if等同的iadd指令是类型安全的,那么iushr指令就是类型安全的。
instructionHasEquivalentTypeRule(iushr, iadd)。
istore, istore_
if一条具有操作数Index的
存储指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
,那么具有操作数Index
和类型int的
存储指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
。
instructionIsTypeSafe(istore(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- storeIsTypeSafe(Environment, Index, int, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令istore_ ,对于0≤n≤3,是类型安全的,if等价的istore指令是类型安全的。
instructionHasEquivalentTypeRule(istore_0, istore(0))。
instructionHasEquivalentTypeRule(istore_1, istore(1))。
instructionHasEquivalentTypeRule(istore_2, istore(2))。
instructionHasEquivalentTypeRule(istore_3, istore(3))。
isub
if等同的iadd指令是类型安全的,那么isub指令就是类型安全的。
instructionHasEquivalentTypeRule(isub, iadd)。
ixor
if等价的iadd指令是类型安全的,那么一条ixor指令就是类型安全的。
instructionHasEquivalentTypeRule(ixor, iadd)。
l2d, l2f, l2i
if一个l2d指令可以有效地从传入的操作数堆栈中弹出long
并替换为double
,产生传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(l2d, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long], double,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一个l2f指令可以有效地从传入的操作数堆栈中弹出long
,并用float
替换,得到传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(l2f, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long], float,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一个l2i指令可以有效地从传入的操作数堆栈中弹出long
并替换为int
,产生传出的类型状态,那么该指令就是类型安全的。
instructionIsTypeSafe(l2i, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
iadd
if人们可以有效地用long
替换传入操作数堆栈中与long
相匹配的类型,产生传出的类型状态,那么一条ladd指令就是类型安全的。
instructionIsTypeSafe(ladd, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long, long], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
laload
if人们可以有效地将传入的操作数栈上与int
和数组long
相匹配的类型替换为long,
产生传出的类型状态,那么一条laoad指令就是类型安全的。
instructionIsTypeSafe(laoad, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, arrayOf(long)], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
iand
if等价的ladd指令是类型安全的,那么一个land指令就是类型安全的。
instructionHasEquivalentTypeRule(land, ladd)。
lastore
一个lastore指令是类型安全的,if可以有效地从传入的操作数栈中弹出与long
、int
和array of long
相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(lastore, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [long, int, arrayOf(long)], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
lcmp
一个lcmp指令是类型安全的,if可以有效地用int
替换传入操作数堆栈中与long
和long
匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(lcmp, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long, long], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
lconst_
if可以有效地将long
类型推到传入的操作数栈上,产生传出的类型状态,那么lconst_0指令就是类型安全的。
instructionIsTypeSafe(lconst_0, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [], long, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条lconst_1指令是类型安全的,那么相当于lconst_0指令是类型安全的。
instructionHasEquivalentTypeRule(lconst_1, lconst_0)。
ldc, ldc_w, ldc2_w
一个带有操作数CP的
ldc指令是类型安全的,ifCP
指的是一个表示Type
类型的实体的常量池条目,其中Type
是int
、float
、String
、Class
、java.lang.invoke.MethodType
或java.lang.invoke.MethodHandle
,并且可以有效地将Type
推到传入的操作数栈,产生传出类型状态。
instructionIsTypeSafe(ldc(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- functor(CP, Tag, _)。isBootstrapLoader(BL)。成员([标签, 类型], [[int, int],[float, float],[string, class('java/lang/String', BL)]。[classConst, class('java/lang/Class', BL)]。[methodTypeConst, class('java/lang/invoke/MethodType', BL)]。[methodHandleConst, class('java/lang/invoke/MethodHandle', BL)] 。]),validTypeTransition(Environment, [], Type, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条ldc_w指令是类型安全的,那么相当于ldc指令是类型安全的。
指令有相等的类型规则(ldc_w(CP), ldc(CP))。
ifCP指的是一个表示Tag
类型的实体的常量池条目,其中Tag
是长的
或双的
,并且可以有效地将Tag
推到传入的操作栈中,产生传出的类型状态,那么一条带有操作数CP的ld
c2_w指令就是类型安全的。
instructionIsTypeSafe(ldc2_w(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- functor(CP, Tag, _)。member(Tag, [long, double])。 validTypeTransition(Environment, [], Tag, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
ldiv
if等价的ladd指令是类型安全的,那么ldiv指令就是类型安全的。
instructionHasEquivalentTypeRule(ldiv, ladd)。
lload, lload_
if一条带有操作数Index
的加载指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
,那么带有操作数Index
和类型long的
加载指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
。
instructionIsTypeSafe(lload(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- loadIsTypeSafe(Environment, Index, long, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令lload_ ,对于0≤n≤3,是类型安全的,if等价的lload指令是类型安全的。
instructionHasEquivalentTypeRule(lload_0, lload(0))。
instructionHasEquivalentTypeRule(lload_1, lload(1))。
instructionHasEquivalentTypeRule(lload_2, lload(2))。
instructionHasEquivalentTypeRule(lload_3, lload(3))。
lmul
if一条lmul指令是类型安全的,那么相当于ladd指令是类型安全的。
instructionHasEquivalentTypeRule(lmul, ladd)。
lneg
if在输入的操作数堆栈中存在一个类型匹配的long,那么
lneg指令就是类型安全的。lneg指令不改变类型状态。
instructionIsTypeSafe(lneg, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [long], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
lookupswitch
if一条lookupswitch指令的键被排序,人们可以有效地从传入的操作数堆栈中弹出int
,产生一个新的类型状态BranchStackFrame
,并且该指令的所有目标都是有效的分支目标,假设BranchStackFrame
是其传入的类型状态。
instructionIsTypeSafe(lookupswitch(Targets, Keys), Environment, _, StackFrame, afterGoto, ExceptionStackFrame) :-sort(Keys, Keys),canPop(StackFrame, [int], BranchStackFrame)。checklist(targetIsTypeSafe(Environment, BranchStackFrame), Targets)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
lor
if等价的ladd指令是类型安全的,那么lor指令就是类型安全的。
instructionHasEquivalentTypeRule(lor, ladd)。
lrem
if等价的ladd指令是类型安全的,那么一条lrem指令就是类型安全的。
instructionHasEquivalentTypeRule(lrem, ladd)。
lreturn
if包围方法的返回类型是long
,那么lreturn指令是类型安全的,并且可以有效地从传入的操作数栈中弹出一个类型匹配的long
。
instructionIsTypeSafe(lreturn, Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :- thisMethodReturnType(Environment, long)。canPop(StackFrame, [long], _PoppedStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
lshl, lshr, lushr
if可以有效地将传入操作数堆栈中的int
和long
类型替换为long类型,
产生传出的类型状态,则lshl指令是类型安全的。
instructionIsTypeSafe(lshl, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, long], long,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if等价的lshl指令是类型安全的,一条lshr指令就是类型安全的。
instructionHasEquivalentTypeRule(lshr, lshl)。
if等同的lshl指令是类型安全的,那么lushr指令就是类型安全的。
instructionHasEquivalentTypeRule(lushr, lshl)。
lstore, lstore_
if一条具有操作数Index的
存储指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
,那么具有操作数Index
和类型long的
存储指令是类型安全的,并且产生一个出场的类型状态NextStackFrame
。
instructionIsTypeSafe(lstore(Index), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- storeIsTypeSafe(Environment, Index, long, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
指令lstore_ ,对于0≤n≤3,if等同的lstore指令是类型安全的。
instructionHasEquivalentTypeRule(lstore_0, lstore(0))。
instructionHasEquivalentTypeRule(lstore_1, lstore(1))。
instructionHasEquivalentTypeRule(lstore_2, lstore(2))。
instructionHasEquivalentTypeRule(lstore_3, lstore(3))。
lsub
if等价的ladd指令是类型安全的,那么lsub指令就是类型安全的。
instructionHasEquivalentTypeRule(lsub, ladd)。
lxor
if等价的ladd指令是类型安全的,那么一条lxor指令就是类型安全的。
指令有相等的类型规则(lxor,ladd)。
monitorenter
一个monitorenter指令是类型安全的,if我们可以有效地从传入的操作数堆栈中弹出一个类型匹配的引用
,产生传出的类型状态。
instructionIsTypeSafe(monitorenter, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :-canPop(StackFrame, [reference], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
monitorexit
一个monitorexit指令是类型安全的,if等价的monitorenter指令是类型安全的。
instructionHasEquivalentTypeRule(monitorexit,monitorenter)。
multianewarray
一个具有操作数CP
和Dim的
多新数组指令是类型安全的,ifCP
指的是一个表示数组类型的常量池条目,其维度大于或等于Dim,Dim
是严格的正数,并且人们可以有效地用CP
表示的类型替换传入操作数栈中的Dim int
类型,从而产生传出的类型状态。
instructionIsTypeSafe(multianewarray(CP, Dim), Environment, _Offset,StackFrame, NextStackFrame, ExceptionStackFrame) :- CP = arrayOf(_)。classDimension(CP, Dimension)。尺寸 >= Dim,Dim > 0。 / 制作一个Dim ints的列表 /findall(int, between(1, Dim, _), IntList),validTypeTransition(Environment, IntList, CP,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一个数组类型的组件类型也是一个数组类型,那么它的尺寸比其组件类型的尺寸多一个。
classDimension(arrayOf(X), Dimension) :-classDimension(X, Dimension1), 尺寸是Dimension1 + 1。 classDimension(_, Dimension) :-尺寸=0。
new
ifCP
指的是一个表示类类型的常量池条目,而uninitialized(Offset)
类型没有出现在传入的操作栈中,并且可以有效地将uninitialized(Offset)
推入传入的操作栈中,并在传入的局部变量中用top
替换uninitialized(Offset),
从而产生传出的类型状态,那么一条新指令的操作数CP
在偏移Offset
处是类型安全的。
instructionIsTypeSafe(new(CP), Environment, Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, OperandStack, Flags)。 CP = class(_, _), NewItem = uninitialized(Offset)。notMember(NewItem, OperandStack)。substitute(NewItem, top, Locals, NewLocals)。validTypeTransition(Environment, [], NewItem,frame(NewLocals, OperandStack, Flags)。NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
替代
谓词在invokespecial(§\invokespecial)的规则中定义。
newarray
ifTypeCode对应
于原始类型ElementType
,那么具有操作数Type的
newarray指令是类型安全的,人们可以有效地将传入的操作数栈上的int
类型替换为 "ElementType
的数组 "类型,产生传出的类型状态。
instructionIsTypeSafe(newarray(TypeCode), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- primitiveArrayInfo(TypeCode, _TypeChar, ElementType, _VerifierType)。validTypeTransition(Environment, [int], arrayOf(ElementType),StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
类型代码和原始类型之间的对应关系由以下谓词指定。
primitiveArrayInfo(4, 0'Z, boolean, int)。
primitiveArrayInfo(5, 0'C, char, int)。
primitiveArrayInfo(6, 0'F, float, float)。
primitiveArrayInfo(7, 0'D, double, double)。
primitiveArrayInfo(8, 0'B, byte, int)。
primitiveArrayInfo(9, 0'S, short, int)。
primitiveArrayInfo(10, 0'I, int, int)。
primitiveArrayInfo(11, 0'J, long, long)。
nop
一个nop指令总是类型安全的。nop指令不影响类型状态。
instructionIsTypeSafe(nop, _Environment, _Offset, StackFrame,堆栈框架, 异常堆栈框架) :-exceptionStackFrame(StackFrame, ExceptionStackFrame)。
pop,pop2
if一个弹出指令可以有效地从传入的操作数堆栈中弹出第1类类型,产生传出的类型状态,那么这个指令就是类型安全的。
instructionIsTypeSafe(pop, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, [Type | Rest], Flags)。Type /= top,sizeOf(Type, 1)。NextStackFrame = frame(Locals, Rest, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条pop2指令是pop2指令的类型安全形式,那么它就是类型安全的。
instructionIsTypeSafe(pop2, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(Locals, InputOperandStack, Flags)。pop2SomeFormIsTypeSafe(InputOperandStack, OutputOperandStack)。NextStackFrame = frame(Locals, OutputOperandStack, Flags),exceptionStackFrame(StackFrame, ExceptionStackFrame)。
if一条pop2指令是类型安全形式的pop2指令,它是类型安全形式1的pop2指令或类型安全形式2的pop2指令。
pop2SomeFormIsTypeSafe(InputOperandStack, OutputOperandStack) :-pop2Form1IsTypeSafe(InputOperandStack, OutputOperandStack)。pop2SomeFormIsTypeSafe(InputOperandStack, OutputOperandStack) :-pop2Form2IsTypeSafe(InputOperandStack, OutputOperandStack)。
pop2指令是一条类型安全的形式1 pop2指令,即可以有效地将两个大小为1的类型从传入的操作数堆栈中弹出,产生传出的类型状态。
pop2Form1IsTypeSafe([Type1, Type2 | Rest], Rest) :-sizeOf(Type1, 1)。sizeOf(Type2, 1)。
pop2指令是一个类型安全的形式2 pop2指令,即可以有效地从传入的操作数堆栈中弹出一个大小为2的类型,产生传出的类型状态。
pop2Form2IsTypeSafe([top, Type | Rest], Rest) :- sizeOf(Type, 2)。
putfiled
有操作数CP
的putfield指令是类型安全的,ifCP
指的是一个常量池条目,表示一个声明类型为FieldType的
字段,声明在FieldClass
类中,并且可以有效地从传入的操作数栈中弹出与FieldType
和FieldClass
相匹配的类型,产生传出类型状态。
instructionIsTypeSafe(putfield(CP), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = field(FieldClass, FieldName, FieldDescriptor)。parseFieldDescriptor(FieldDescriptor, FieldType)。 canPop(StackFrame, [FieldType], PoppedFrame)。passesProtectedCheck(Environment, FieldClass, FieldName,FieldDescriptor, PoppedFrame)。currentClassLoader(Environment, CurrentLoader)。canPop(StackFrame, [FieldType, class(FieldClass, CurrentLoader)],NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
putstatic
一个带有操作数CP的
putstatic指令是类型安全的,ifCP
指的是一个表示字段的常量池条目,其声明的类型是FieldType
,并且可以有效地从传入的操作数栈中弹出一个与FieldType
匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(putstatic(CP), _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- CP = field(_FieldClass, _FieldName, FieldDescriptor)。parseFieldDescriptor(FieldDescriptor, FieldType)。canPop(StackFrame, [FieldType], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
return
if一个返回指令所包围的方法声明了一个无效的
返回类型,那么这个返回指令是类型安全的,并且要么。
· 包围的方法不是<init>
方法,或
· 在指令发生时,已经完全初始化了。
instructionIsTypeSafe(return, Environment, _Offset, StackFrame,afterGoto, ExceptionStackFrame) :- thisMethodReturnType(Environment, void)。StackFrame = frame(_Locals, _OperandStack, Flags)。notMember(flagThisUninit, Flags)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
saload
一个saload指令是类型安全的,if人们可以有效地用int
替换传入操作数栈上与int
匹配的类型和短
数组,产生传出的类型状态。
instructionIsTypeSafe(saroad, Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [int, arrayOf(short)], int,StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
sastore
一个sastore指令是类型安全的,if可以有效地从传入的操作数栈中弹出与int
、int
和array of short
相匹配的类型,产生传出的类型状态。
instructionIsTypeSafe(sastore, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- canPop(StackFrame, [int, int, arrayOf(short)], NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
sipush
if可以有效地将int
类型推到传入的操作数栈上,产生传出的类型状态,那么一条sipush指令就是类型安全的。
instructionIsTypeSafe(sipush(_Value), Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- validTypeTransition(Environment, [], int, StackFrame, NextStackFrame)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
swap
一个交换指令是类型安全的,if可以有效地替换两个第1类类型,Type1
和Type2
,在传入的操作数堆栈中,用Type2
和Type1的
类型产生传出的类型状态。
instructionIsTypeSafe(swap, _Environment, _Offset, StackFrame,NextStackFrame, ExceptionStackFrame) :- StackFrame = frame(_Locals, [Type1, Type2 | Rest], _Flags)。sizeOf(Type1, 1)。sizeOf(Type2, 1)。NextStackFrame = frame(_Locals, [Type2, Type1 | Rest], _Flags)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
tableswitch
if一条表转换指令的键被排序,人们可以有效地从传入的操作数堆栈中弹出int
,产生一个新的类型状态BranchStackFrame
,并且该指令的所有目标都是有效的分支目标,假设BranchStackFrame
是它们传入的类型状态。
instructionIsTypeSafe(tableswitch(Targets, Keys), Environment, _Offset,StackFrame, afterGoto, ExceptionStackFrame) :- sort(Keys, Keys), canPop(StackFrame, [int], BranchStackFrame)。checklist(targetIsTypeSafe(Environment, BranchStackFrame), Targets)。exceptionStackFrame(StackFrame, ExceptionStackFrame)。
wide
广义的指令与它们所扩大的指令遵循同样的规则。
instructionHasEquivalentTypeRule(wide(WidenedInstruction),加宽教学)。
4.10.2.通过类型推理进行验证
一个不包含StackMapTable
属性的类
文件(其版本号必然是49.0或以下)必须使用类型推理进行验证。
4.10.2.1.通过类型推理进行验证的过程
在链接过程中,验证器通过对每个方法进行数据流分析,检查类
文件中每个方法的代码属性的代码
数组。验证器确保在程序的任何给定点,无论采取何种代码路径到达该点,以下所有情况都是真的。
· 操作数栈的大小总是相同的,包含相同类型的值。
· 除非已知局部变量包含一个适当类型的值,否则不会对其进行访问。
· 方法是用适当的参数来调用的。
· 字段只使用适当类型的值来分配。
· 所有操作码在操作数栈和局部变量数组中都有适当类型的参数。
出于效率的考虑,某些原则上可以由验证器执行的测试被推迟到方法的代码第一次被实际调用的时候。这样一来,验证器就避免了加载类
文件,除非它必须这样做。
例如,if一个方法调用了另一个返回A类实例的方法,而该实例只被分配给同一类型的字段,那么验证器就不会去检查A类是否真的存在。然而,if它被分配给B类型的字段,则必须加载A和B的定义以确保A是B的子类。
4.10.2.2.比特码验证器
每种方法的代码都是独立验证的。首先,组成代码的字节被分解成一连串的指令,并将每条指令开始的代码
数组的索引放在一个数组中。然后,验证器第二次浏览代码并解析指令。在这个过程中,会建立一个数据结构来保存方法中每个Java虚拟机指令的信息。每条指令的操作数(if有的话)被检查以确保它们是有效的。例如。
· 分支必须在该方法的代码
阵列范围内。
· 所有控制流指令的目标都是一条指令的开始。在宽指令的情况下,宽指令的操作码被认为是指令的开始,而由该宽指令修改的操作码不被认为是指令的开始。不允许向指令的中间分支。
· 任何指令都不能访问或修改一个索引大于或等于其方法表明分配的局部变量数量的局部变量。
· 所有对常量池的引用必须是对适当类型的条目的引用。(例如,指令getfield必须引用一个字段)。
· 代码不会在一条指令的中间结束。
· 执行不能落在代码的末端。
· 对于每个异常处理程序,处理程序所保护的代码的起点和终点必须是在指令的开头,或者在终点的情况下,紧贴着代码的结束。起始点必须在结束点之前。异常处理程序的代码必须从一条有效的指令开始,而且不能从被宽指令修改的操作码开始。
对于该方法的每一条指令,验证器都会在执行该指令之前记录操作数栈的内容和局部变量数组的内容。对于操作数栈,它需要知道堆栈的高度和上面每个值的类型。对于每个局部变量,它需要知道该局部变量内容的类型,或者该局部变量包含一个不可用或未知的值(它可能是未初始化的)。在确定操作数堆栈上的值类型时,字节码验证器不需要区分积分类型(例如,字节
、短
、char
)。
接下来,一个数据流分析器被初始化。对于方法的第一条指令,代表参数的局部变量最初包含方法的类型描述符所指示的值;操作数栈是空的。所有其他局部变量包含一个非法值。对于其他尚未检查的指令,没有关于操作数栈或局部变量的信息。
最后,数据流分析器被运行。对于每条指令,一个 "改变 "位表明这条指令是否需要被查看。最初,只有第一条指令的 "改变 "位被设置。数据流分析器执行以下循环。
\1. 选择一个 "改变 "位被设置的Java虚拟机指令。if没有指令的 "改变 "位被设置,该方法已经成功验证。否则,关闭所选指令的 "更改 "位。
\2. 通过以下方式模拟指令对操作数栈和局部变量阵列的影响。
· if指令使用操作数堆栈中的值,请确保堆栈中有足够数量的值,并且堆栈中的最高值是适当的类型。否则,验证失败。
· if该指令使用局部变量,请确保指定的局部变量包含适当类型的值。否则,验证失败。
· if该指令将数值推到操作数堆栈上,确保操作数堆栈上有足够的空间容纳新的数值。将指定的类型添加到建模的操作数栈的顶部。
· if该指令修改了一个局部变量,则记录该局部变量现在包含新的类型。
\3. 确定可以跟随当前指令的指令。后续指令可以是以下之一。
· 下一条指令,if当前指令不是无条件的控制转移指令(例如,goto、return或athrow)。if有可能 "脱落 "该方法的最后一条指令,则验证失败。
· 有条件或无条件的分支或转换的目标(s)。
· 该指令的任何异常处理程序。
\4. 将当前指令执行结束时的操作数栈和局部变量阵列的状态合并到每个后续指令中。
在控制权转移到异常处理程序的特殊情况下,操作数栈被设置为包含一个由异常处理程序信息指示的异常类型的单一对象。在操作数堆栈上必须有足够的空间来容纳这个单一的值,就像一条指令把它推了上去一样。
· if这是第一次访问后继指令,记录在步骤2和3中计算出的操作数 堆和局部变量值是执行后继指令前的操作数堆和局部变量阵列的状态。设置后继指令的 "改变 "位。
· if继任指令以前被看到过,将步骤2和3中计算的操作数堆栈和局部变量值合并到已经存在的值中。if对数值有任何修改,设置 "改变 "位。
\5. 继续进行第1步。
要合并两个操作数栈,每个栈上的值的数量必须是相同的。然后,比较两个堆栈上的相应数值,并计算出合并后的堆栈上的数值,如下所示。
· if一个值是原始类型,那么相应的值必须是相同的原始类型。合并后的值就是原始类型。
· if一个值是一个非数组引用类型,那么相应的值必须是一个引用类型(数组或非数组)。合并后的值是对两个引用类型的第一个共同超类型的实例的引用。(这样的引用类型总是存在的,因为Object
类型是所有类、接口和数组类型的超类型)。
例如,Object
和String
可以被合并,结果是Object
。同样地,Object
和String[]可以合并
;结果还是Object
。甚至Object
和int[]也
可以合并,或者String
和int[]也可以合并
;两者的结果都是Object。
· if相应的值都是数组的引用类型,那么它们的尺寸会被检查。if数组类型有相同的尺寸,那么合并后的值是对一个数组类型实例的引用
,这个数组类型是两个数组类型的第一个共同超类型。(if其中一个或两个数组类型有一个原始的元素类型,那么Object
被用作元素类型。)if数组类型有不同的尺寸,那么合并后的值是对一个数组类型实例的引用,这个数组类型的
尺寸是两者中较小的那个;if较小的数组类型是Cloneable或java.io.Serializable,那么
元素类型是Cloneable
或java.io.Serializable
,否则是Object
。
例如,Object[]
和String[]
可以被合并,结果是Object[]
。Cloneable[]
和String[]
可以合并,或者java.io.Serializable[]
和String[]
;结果分别是Cloneable[]
和java.io.Serializable[]
。甚至int[]
和String[]也
可以合并;结果是Object[]
,因为在计算第一个公共超类型时,使用Object
而不是int
。
由于数组类型可以有不同的尺寸,Object[]
和String[][]
可以被合并,或者Object[][]
和String[]
;在这两种情况下,结果都是Object[]
。Cloneable[]
和String[][]可以合并
;结果是Cloneable[]
。最后,Cloneable[][]
和String[]可以被合并
;结果是Object[]
。
if操作数栈不能被合并,则方法的验证失败。
要合并两个局部变量数组状态,需要比较相应的局部变量对。合并后的局部变量的值是用上述规则计算出来的,只是相应的值允许是不同的原始类型。在这种情况下,验证器会记录合并后的局部变量包含一个不可用的值。
if数据流分析器在一个方法上运行而没有报告验证失败,那么这个方法已经被类
文件验证器成功验证了。
某些指令和数据类型使数据流分析器变得复杂。我们现在更详细地研究这些指令。
4.10.2.3.long
和double
类型的值
long
和double
类型的值在验证过程中被特别处理。
每当一个long
或double
类型的值被移到索引n处的局部变量中时,索引n+1会被特别标记,以表明它已经被索引n处的值保留,并且不得被用作局部变量的索引。之前在索引n+1处的任何值都将无法使用。
每当一个值被移到索引n的局部变量中时,索引n-1会被检查,看它是否是long
或double
类型的值的索引。if是的话,索引n-1处的局部变量就会被改变,以表明它现在包含一个不可用的值。由于索引n处的局部变量已经被覆盖,索引n-1处的局部变量不能代表long
或double
类型的值。
处理操作数堆栈上的long
或double
类型的值比较简单;验证器将它们视为堆栈上的单个值。例如,dadd操作码(添加两个双倍
值)的验证代码检查堆栈上的前两个项目是否都是双倍
类型。当计算操作数栈的长度时,long
和double
类型的值的长度为2。
操作堆栈的非类型指令必须将long
和double
类型的值视为原子值(不可分割)。例如,if堆栈上的最高值是双数
,并且遇到pop或dup这样的指令,验证器会报告失败。必须使用指令pop2或dup2来代替。
4.10.2.4.实例初始化方法和新创建的对象
创建一个新的类实例是一个多步骤的过程。的说法。
...
new myClass(i, j, k);
...
可以通过以下方式实现。
...
new #1 // 为myClass分配未初始化的空间
dup // 复制操作数堆栈上的对象
iload_1 // 推动i
iload_2 // 推动j
iload_3 // 推动k
invokespecial #5 //调用myClass。 <init>
...
这个指令序列将新创建和初始化的对象留在操作数栈的顶部。(在第3节(为Java虚拟机编译)中给出了编译到Java虚拟机指令集的其他例子)。
类myClass
的实例初始化方法(§2.9)将新的未初始化的对象视为其局部变量0中的this
参数。在该
方法对myClass
或其直接超类的另一个实例初始化方法进行调用之前,该方法可以对其进行的
唯一操作是分配在myClass
中声明的字段。
在对实例方法进行数据流分析时,验证器将局部变量0初始化为包含当前类的一个对象,或者,对于实例初始化方法,局部变量0包含一个特殊类型,表示一个未初始化的对象。在这个对象上调用适当的实例初始化方法(来自当前类或其直接的超类)后,这个特殊类型在验证器的操作栈模型和局部变量数组中的所有出现都被替换成当前类的类型。验证器拒绝那些在新对象被初始化之前就使用它的代码,或者多次初始化该对象的代码。此外,它还确保该方法的每个正常返回都调用了该方法的类或直接超类中的实例初始化方法。
同样地,一个特殊的类型被创建并推送到验证器的操作栈模型上,作为Java虚拟机指令new的结果。该特殊类型表示创建类实例的指令和创建的未初始化类实例的类型。当在未初始化类实例的类中声明的实例初始化方法在该类实例上被调用时,所有出现的特殊类型都被该类实例的预期类型所取代。随着数据流分析的进行,这种类型的改变可能会传播到随后的指令中。
指令号需要作为特殊类型的一部分来存储,因为在操作数栈上可能同时存在多个尚未初始化的类实例。例如,实现的Java虚拟机指令序列。
new InputStream(new Foo(), new InputStream("foo"))
在操作栈上可能同时有两个未初始化的InputStream实例
。当一个实例初始化方法在一个类的实例上被调用时,只有那些在操作堆栈或局部变量数组中出现的与该类实例相同的特殊类型被替换。
4.10.2.5.异常情况和最后
为了实现try-finally
结构,生成版本号为50.0或以下的类
文件的Java编程语言的编译器可以使用异常处理设施和两个特殊指令:jsr(“跳转到子程序”)和ret(“从子程序返回”)。finally
子句被编译为其方法的Java虚拟机代码中的一个子程序,很像异常处理程序的代码。当调用子程序的jsr指令被执行时,它将其返回地址,即正在执行的jsr之后的指令的地址,作为returnAddress
类型的值推到操作栈中。子程序的代码将返回地址存储在一个局部变量中。在子程序结束时,ret指令从局部变量中获取返回地址,并将控制权转移给返回地址的指令。
控制可以通过几种不同的方式转移到final
子句(可以调用final
子程序)。iftry
子句正常完成,在评估下一个表达式之前通过jsr指令调用final子程序。在try子句
内部的break
或continue
将控制权转移到try
子句之外,会先执行一个jsr到final
子句的代码。iftry
子句执行了一个return,编译后的代码会做以下工作。
\1. 将返回值(if有的话)保存在一个局部变量中。
\2. 执行一个jsr到最后
子句的代码。
\3. 从finally
子句返回后,返回保存在本地变量中的值。
编译器设置了一个特殊的异常处理程序,它可以捕获任何由try
子句抛出的异常。if在try
子句中抛出了一个异常,这个异常处理程序会做以下工作。
\1. 将异常保存在一个局部变量中。
\2. 执行一个jsr到finally
子句。
\3. 在从finally
子句返回时,重新抛出异常。
关于try-finally
结构的实现的更多信息,请参见§3.13。
finally
子句的代码给验证者带来了一个特殊的问题。通常情况下,if某条指令可以通过多条路径到达,而某一局部变量通过这些多条路径包含不兼容的值,那么该局部变量就无法使用。然而,一个最终
子句可能从几个不同的地方被调用,产生几个不同的情况。
· 来自异常处理程序的调用可能有某个局部变量包含一个异常。
· 实现返回的调用可能有一些包含返回值的局部变量。
· 来自try
子句底部的调用可能会在同一个局部变量中产生一个不确定的值。
finally
子句的代码本身可能会通过验证,但在完成更新ret指令的所有后继者后,验证者会注意到异常处理程序期望持有的局部变量,或者返回代码期望持有的返回值,现在包含一个不确定的值。
验证包含finally
子句的代码是很复杂的。其基本思想如下。
· 每条指令都会跟踪到达该指令所需的jsr目标列表。对于大多数代码,这个列表是空的。对于最后
子句的代码内的指令,它的长度为1。对于多重嵌套的finally
代码(极其罕见!),它的长度可能超过1。
· 对于每条指令和到达该指令所需的每个jsr,都有一个位向量,记录了自执行jsr指令以来访问或修改的所有局部变量。
· 当执行实现从子程序返回的ret指令时,必须只有一个可能的子程序,该指令可以从中返回。两个不同的子程序不能 "合并 "执行到一个ret指令中。
· 为了对ret指令进行数据流分析,使用了一个特殊的程序。由于验证器知道该指令必须从哪个子程序返回,它可以找到所有调用该子程序的jsr指令,并将ret指令发出时的操作栈和局部变量数组的状态合并到jsr后面指令的操作栈和局部变量数组中。合并使用一套特殊的局部变量值。
o 对于任何位向量(上面构建的)表明已被子程序访问或修改的局部变量,使用ret时局部变量的类型。
o 对于其他局部变量,在jsr指令前使用局部变量的类型。
4.11.Java虚拟机的局限性
Java虚拟机的以下限制是隐含在类
文件格式中的。
· 每个类或每个接口的常量池被ClassFile
结构(§4.1)的16位constant_pool_count
字段限制为65535条。这是对单个类或接口的总复杂性的一个内部限制。
· 一个类或接口可以声明的字段数量被ClassFile
结构的field_count
项的大小限制在65535以内(§4.1)。
注意,ClassFile
结构的 fields_count
项的值不包括从超类或超接口继承的字段。
· 一个类或接口可以声明的方法的数量被ClassFile
结构的methods_count
项的大小限制在65535以内(§4.1)。
请注意,ClassFile
结构的 methods_count
项的值不包括从超类或超接口继承的方法。
· 一个类或接口的直接超接口的数量被ClassFile
结构的interface_count
项的大小限制在65535以内(§4.1)。
· 在调用方法(§2.6)时创建的框架的局部变量数组中,局部变量的最大数量被Code
属性(§4.7.3)中给出方法代码的max_locals
项的大小以及Java虚拟机指令集的16位局部变量索引限制为65535。
请注意,long
和double
类型的值被认为是保留了两个局部变量,并为max_locals
值贡献了两个单位,所以使用这些类型的局部变量会进一步降低这个限制。
· 一个框架中的操作数栈的大小(§2.6)被代码
属性的max_stack
字段限制为65535个值(§4.7.3)。
请注意,long
和double
类型的值被认为对max_stack
值有两个单位的贡献,所以在操作数栈上使用这些类型的值会进一步减少这个限制。
· 方法参数的数量被方法描述符的定义限制为255个(§4.3.3),在实例或接口方法调用的情况下,该
限制包括一个单元。
请注意,方法描述符是根据方法参数长度的概念定义的,其中long
或double
类型的参数对长度有两个单位的贡献,所以这些类型的参数会进一步降低限制。
· 字段和方法名称、字段和方法描述符以及其他常量字符串值(包括由ConstantValue
(§4.7.2)属性引用的那些)的长度被CONSTANT_Utf8_info
结构(§4.4.7)的16位无符号长度
项限制为65535个字符。
请注意,限制的是编码中的字节数,而不是编码后的字符数。UTF-8对一些字符的编码使用两个或三个字节。因此,包含多字节字符的字符串会受到进一步限制。
· 数组中的维数被限制在255以内,这是由multianewarray指令的维数操作码的大小和multianewarray、anewarray和newarray指令的约束所决定的(§4.9.1, §4.9.2)。
第五章 加载、链接和初始化
Java虚拟机动态地加载、链接和初始化类和接口。加载是寻找具有特定名称的类或接口类型的二进制表示,并从该二进制表示创建一个类或接口的过程。链接是将一个类或接口结合到Java虚拟机的运行时状态的过程,以便它可以被执行。类或接口的初始化包括执行类或接口的初始化方法< clinit>(§2.9)。
在本章中,第 5.1 节描述了 Java 虚拟机如何从类或接口的二进制表示法中导出符号引用。§第 5.2 节解释了 Java 虚拟机是如何首先启动加载、链接和初始化过程的。§第5.3节规定了类和接口的二进制表示如何由类加载器加载,以及类和接口如何被创建。第5.4节描述了链接的情况。§第5.5节详细说明了类和接口是如何被初始化的。§第5.6节介绍了绑定本地方法的概念。最后,第5.7节描述了Java虚拟机何时退出。
5.1.运行时常量库
Java虚拟机维护着一个每个类型的常量池(§2.5.5),这是一个运行时数据结构,为传统编程语言实现的符号表的许多目的服务。
类或接口的二进制表示中的constant_pool表(§4.4)在类或接口创建时被用来构建运行时常量池(§5.3)。运行时常量池中的所有引用最初都是符号化的。运行时常量池中的符号引用是由类或接口的二进制表示法中的结构衍生出来的,具体如下。
· 对类或接口的符号引用是由类或接口的二进制表示法中的CONSTANT_Class_info结构(§4.4.1)导出的。这样的引用以Class.getName方法返回的形式给出了该类或接口的名称,也就是。
o 对于一个非数组类或接口,名称是该类或接口的二进制名称(§4.2.1)。
o 对于一个有n个维度的数组类,名称以n次出现的ASCII"["字符开始,后面是元素类型的表示。
§ 如果元素类型是一个原始类型,它由相应的字段描述符表示(§4.3.2)。
§ 否则,如果元素类型是一个参考类型,则用ASCII "L “字符表示,后面是元素类型的二进制名称(§4.2.1),后面是ASCII”;"字符。
每当本章提到一个类或接口的名称时,应该理解为以Class.getName方法所返回的形式。
· 对类或接口的字段的符号引用是由类或接口的二进制表示中的CONSTANT_Fieldref_info结构(§4.4.2)导出的。这样的引用给出了字段的名称和描述符,以及对要找到该字段的类或接口的符号引用。
· 对一个类的方法的符号引用是由一个类或接口的二进制表示中的CONSTANT_Methodref_info结构(§4.4.2)派生的。这样的引用给出了方法的名称和描述符,以及找到该方法的类的符号引用。
· 对接口方法的符号引用是由类或接口的二进制表示中的CONSTANT_InterfaceMethodref_info结构(§4.4.2)派生的。这样的引用给出了接口方法的名称和描述符,以及对找到该方法的接口的符号引用。
· 对方法句柄的符号引用来自于类或接口的二进制表示中的CONSTANT_MethodHandle_info结构(§4.4.8)。这样的引用给出了对一个类或接口的字段,或者一个类的方法,或者一个接口的方法的符号引用,这取决于方法柄的种类。
· 对方法类型的符号引用来自于类或接口的二进制表示中的CONSTANT_MethodType_info结构(§4.4.9)。这样的引用给出了一个方法描述符(§4.3.3)。
· 对调用站点指定器的符号引用来自类或接口的二进制表示中的CONSTANT_InvokeDynamic_info结构(§4.4.10)。这样的引用给出。
o 一个对方法句柄的符号引用,它将作为 invokedynamic 指令的引导方法(§invokedynamic)。
o 一系列的符号引用(类、方法类型和方法句柄)、字符串和运行时常量值,它们将作为引导方法的静态参数。
o 一个方法名称和方法描述符。
此外,某些不属于符号引用的运行时值是由常量表(constant_pool)中的项目派生出来的。
· 字符串字面是对String类实例的引用,在类或接口的二进制表示中,它来自CONSTANT_String_info结构(§4.4.3)。CONSTANT_String_info结构给出了构成字符串字面的Unicode代码点序列。
Java编程语言要求相同的字符串字头(即包含相同代码点序列的字头)必须指向String类的相同实例(JLS §3.10.5)。此外,如果在任何字符串上调用方法String.intern,其结果是对同一类实例的引用,如果该字符串作为字面意义出现,则会返回该类实例。因此,下面的表达式的值必须是true。
(“a” + “b” + “c”).intern() == “abc”
为了得出一个字符串字面,Java虚拟机检查了CONSTANT_String_info结构所给出的代码点序列。
o 如果方法String.intern先前已经在一个包含与CONSTANT_String_info结构给出的Unicode码位序列相同的String类实例上被调用,那么字符串字面推导的结果是对同一个String类实例的引用。
o 否则,将创建一个新的String类实例,包含CONSTANT_String_info结构给出的Unicode代码点序列;对该类实例的引用是字符串字面推导的结果。最后,新的String实例的intern方法被调用。
· 在类或接口的二进制表示中,运行时常量值来自CONSTANT_Integer_info、CONSTANT_Float_info、CONSTANT_Long_info或CONSTANT_Double_info结构(§4.4.4、§4.4.5)。
请注意,CONSTANT_Float_info结构代表IEEE 754单格式的值,CONSTANT_Double_info结构代表IEEE 754双格式的值(§4.4.4, §4.4.5)。因此,从这些结构中导出的运行时常量值必须是可以分别使用IEEE 754单格式和双格式表示的值。
类或接口的二进制表示法的constant_pool表中的其余结构–CONSTANT_NameAndType_info和CONSTANT_Utf8_info结构(§4.4.6, §4.4.7)–只在派生类、接口、方法、字段、方法类型和方法句柄的符号引用,以及派生字符串和调用站点指定器时间接使用。
5.2.Java虚拟机的启动
Java虚拟机通过创建一个初始类来启动,这个初始类是用bootstrap类加载器(§5.3.1)以一种与实现有关的方式指定的。然后Java虚拟机链接初始类,初始化它,并调用公共类方法void main(String[])。这个方法的调用驱动所有进一步的执行。执行构成main方法的Java虚拟机指令可能会导致链接(并因此创建)其他类和接口,以及调用其他方法。
在Java虚拟机的实现中,初始类可以作为一个命令行参数被提供。或者,实现可以提供一个初始类来设置一个类加载器,然后再加载一个应用程序。对初始类的其他选择是可能的,只要它们与上一段给出的规范一致。
5.3.创建和加载
由名称N表示的类或接口C的创建包括在Java虚拟机的方法区(§2.5.4)构建C的特定实现的内部表示。类或接口的创建由另一个类或接口D触发,它通过其运行时常量池引用C。类或接口的创建也可以由D调用某些Java SE平台类库(§2.12)中的方法来触发,如反射。
如果C不是一个数组类,它是通过使用类加载器加载C的二进制表示(§4(类文件格式))而创建的。阵列类没有外部二进制表示;它们是由Java虚拟机而不是由类加载器创建的。
有两种类加载器:由Java虚拟机提供的引导类加载器,以及用户定义的类加载器。每个用户定义的类加载器都是抽象类ClassLoader的一个子类的实例。应用程序使用用户定义的类加载器,以扩展Java虚拟机动态加载的方式,从而创建类。用户定义的类加载器可以用来创建来自用户定义的源的类。例如,一个类可以通过网络下载,即时生成,或者从一个加密文件中提取。
一个类加载器L可以通过直接定义或委托给另一个类加载器来创建C。如果L直接创建了C,我们说L定义了C,或者说,L是C的定义加载器。
当一个类加载器委托给另一个类加载器时,发起加载的加载器不一定是完成加载并定义该类的加载器。如果L创造了C,无论是通过直接定义还是通过委托,我们说L启动了C的加载,或者说,L是C的一个启动加载器。
在运行时,一个类或接口不是由它的名字决定的,而是由一对:它的二进制名称(§4.2.1)和它的定义类加载器。每个这样的类或接口都属于一个运行时包。一个类或接口的运行时包是由该类或接口的包名和定义类加载器决定的。
Java虚拟机使用三个程序中的一个来创建用N表示的类或接口C。
· 如果N表示一个非数组类或一个接口,则使用以下两种方法中的一种来加载,从而创建C。
o 如果D是由bootstrap类加载器定义的,那么bootstrap类加载器就开始加载C(§5.3.1)。
o 如果D是由用户定义的类加载器定义的,那么同一个用户定义的类加载器会启动C的加载(§5.3.2)。
· 否则N表示一个数组类。一个数组类是由Java虚拟机直接创建的(§5.3.3),而不是由一个类加载器创建的。然而,在创建数组类C的过程中,D的定义类加载器被使用。
如果在类加载过程中发生错误,那么必须在程序中(直接或间接)使用被加载的类或接口的地方抛出一个LinkageError的子类的实例。
如果 Java 虚拟机在验证(§5.4.1)或解析(§5.4.3)(但不是初始化(§5.5))期间试图加载一个类 C,并且用于启动加载 C 的类加载器抛出 ClassNotFoundException 的实例,那么 Java 虚拟机必须抛出 NoClassDefFoundError 的实例,其原因就是 ClassNotFoundException 的实例。
(这里的一个微妙之处在于,加载超类的递归类是作为解析的一部分进行的(§5.3.5,步骤3)。因此,由于类加载器未能加载超类而产生的ClassNotFoundException必须被包装成NoClassDefFoundError)。
一个乖巧的类加载器应该保持三个属性。
· 鉴于相同的名字,一个好的类加载器应该总是返回相同的Class对象。
· 如果一个类加载器L1 将一个类C的加载委托给另一个加载器L2 ,那么对于任何作为C的直接超类或直接超接口出现的类型T,或者作为C中一个字段的类型,或者作为C中一个方法或构造函数的形式参数的类型,或者作为C中一个方法的返回类型,L1 和L2 应该返回同一个类对象。
· 如果一个用户定义的类加载器预取了类和接口的二进制表示,或者一起加载了一组相关的类,那么它必须只反映程序中的加载错误,而这些错误在没有预取或分组加载的情况下是可以产生的。
我们有时会用<N, Ld >的符号来表示一个类或接口,其中N表示该类或接口的名称,Ld 表示该类或接口的定义装载器。
我们还将使用符号NLi 来表示一个类或接口,其中N表示该类或接口的名称,Li 表示该类或接口的启动加载器。
5.3.1.使用Bootstrap类加载器加载
下面的步骤用于加载并由此创建非数组类或接口C,用N表示,使用bootstrap类加载器。
首先,Java虚拟机确定自举类加载器是否已经被记录为由N表示的类或接口的启动加载器,如果是,这个类或接口就是C,不需要创建类。
否则,Java虚拟机会将参数N传递给bootstrap类加载器上的一个方法的调用,以便以一种与平台有关的方式搜索C的所谓表示。通常,一个类或接口将使用分层文件系统中的一个文件来表示,类或接口的名称将被编码在文件的路径名中。
请注意,不能保证找到的所谓表征是有效的,或者是C的表征,这个阶段的加载必须检测到以下错误。
· 如果没有找到C的所谓代表,加载会抛出一个ClassNotFoundException的实例。
然后,Java虚拟机试图使用引导类加载器从所谓的表示中使用§5.3.5中的算法推导出一个由N表示的类。这个类就是C。
5.3.2.使用用户定义的类加载器加载
下面的步骤用于加载,从而使用用户定义的类加载器L创建由N表示的非数组类或接口C。
首先,Java虚拟机确定L是否已经被记录为一个由N表示的类或接口的启动加载器,如果是,这个类或接口就是C,不需要创建类。
否则,Java 虚拟机对 L 调用 loadClass(N),调用返回的值是创建的类或接口 C。本节的其余部分将更详细地描述这个过程。
当类加载器L的loadClass方法被调用,并输入要加载的类或接口C的名称N时,L必须执行以下两个操作之一,以便加载C。
\1. 类加载器L可以创建一个代表C的字节数组,作为ClassFile结构的字节(§4.1);然后它必须调用ClassLoader类的defineClass方法。调用defineClass会使Java虚拟机使用L从字节数组中使用§5.3.5中的算法衍生出一个由N表示的类或接口。
\2. 类加载器L可以将C的加载委托给其他的类加载器L’。这可以通过直接或间接地将参数N传递给L’上的一个方法(通常是loadClass方法)的调用来实现。调用的结果是C。
在(1)或(2)中,如果类加载器L由于任何原因无法加载一个由N表示的类或接口,它必须抛出一个ClassNotFoundException的实例。
从 JDK 1.1 版开始,Oracle 的 Java 虚拟机实现已经调用了类加载器的 loadClass 方法,以使其加载一个类或接口。loadClass 的参数是要加载的类或接口的名称。还有一个loadClass方法的双参数版本,其中第二个参数是一个布尔值,表示该类或接口是否要被链接。在 JDK 1.0.2 版中只提供了双参数版本,Oracle 的 Java 虚拟机实现依靠它来链接加载的类或接口。从 JDK 1.1 版开始,Oracle 的 Java 虚拟机实现直接链接类或接口,不依赖类加载器。
5.3.3.创建数组类
下面的步骤是使用类加载器L来创建用N表示的数组类。类加载器L可以是自举类加载器,也可以是用户定义的类加载器。
如果L已经被记录为一个与N具有相同组件类型的数组类的启动加载器,该类就是C,不需要创建数组类。
否则,将执行以下步骤来创建C。
\1. 如果组件类型是一个引用类型,本节(§5.3)的算法将使用类加载器L递归应用,以便加载并由此创建C的组件类型。
\2. Java虚拟机以指定的组件类型和维数创建一个新的数组类。
如果组件类型是一个参考类型,C被标记为由组件类型的定义类加载器定义。否则,C将被标记为由引导类加载器定义的。
在任何情况下,Java虚拟机都会记录L是C的启动加载器(§5.3.4)。
如果组件类型是一个引用类型,数组类的可访问性由其组件类型的可访问性决定。否则,数组类的可访问性是公共的。
5.3.4.加载限制条件
在有类加载器的情况下,确保类型安全链接需要特别注意。当两个不同的类加载器开始加载一个由N表示的类或接口时,N这个名字可能在每个加载器中表示一个不同的类或接口。
当一个类或接口C = <N1 , L1 >对另一个类或接口D = <N2 , L2 >的字段或方法进行符号引用时,该符号引用包括一个描述符,指定了字段的类型,或方法的返回和参数类型。至关重要的是,在字段或方法描述符中提到的任何类型名称N在被L1 加载时和被L2 加载时都表示同一类或接口。
为了确保这一点,Java 虚拟机在准备(§5.4.2)和解决(§5.4.3)期间施加了形式为 NL1 = NL2 的\加载约束。\为了执行这些约束,Java 虚拟机将在某些规定的时间(见第 5.3.1 节、第 5.3.2 节、第 5.3.3 节和第 5.3.5 节)记录一个特定的加载器是一个特定类的启动加载器。在记录了某个加载器是某个类的启动加载器后,Java 虚拟机必须立即检查是否有任何加载约束被违反。如果是这样,记录会被收回,Java 虚拟机会抛出一个 LinkageError,导致记录发生的加载操作也会失败。
同样,在施加了一个加载约束后(见第 5.4.2 节、第 5.4.3.2 节、第 5.4.3.3 节和第 5.4.3.4 节),Java 虚拟机必须立即检查是否有任何加载约束被违反了。如果是这样,新施加的加载约束被收回,Java 虚拟机抛出一个 LinkageError,导致约束被施加的操作(根据情况是解析或准备)失败。
这里描述的情况是Java虚拟机检查是否有任何加载约束被违反的唯一时间。当且仅当以下四个条件都成立时,加载约束被违反。
· 存在一个加载器L,使得L被Java虚拟机记录为一个名为N的类C的启动加载器。
· 存在一个加载器L’,使得L’被Java虚拟机记录为一个名为N的类C’的启动加载器。
· 由强加的约束集(反式封闭)定义的等价关系意味着NL = NL’ 。
· C ≠ C’。
对类加载器和类型安全的全面讨论已经超出了本规范的范围。关于更全面的讨论,读者可以参考Sheng Liang和Gilad Bracha的《Java虚拟机中的动态类加载》(1998年ACM SIGPLAN面向对象编程系统、语言和应用会议论文集)。
5.3.5.从类的文件表示中派生出一个类
下面的步骤用来为非数组类或接口C派生一个类对象,该类对象由N使用加载器L从类文件格式的所谓表示中派生出来。
- 首先,Java虚拟机确定它是否已经记录了L是一个由N表示的类或接口的启动加载器,如果是,这个创建尝试是无效的,加载会抛出一个LinkageError。
\2. 否则,Java虚拟机会尝试解析所谓的表示法。然而,声称的表示法事实上可能不是C的有效表示法。
这个阶段的加载必须检测出以下错误。
· 如果声称的表示不是一个ClassFile结构(§4.1, §4.8),加载会抛出一个ClassFormatError的实例。
· 否则,如果声称的表示不属于支持的主要或次要版本(§4.1),加载会抛出一个UnsupportedClassVersionError实例。
UnsupportedClassVersionError 是 ClassFormatError 的一个子类,它的出现是为了方便识别因试图加载一个使用不支持的类文件格式的类而引起的 ClassFormatError。在 JDK 1.1 版和更早的版本中,如果出现不支持的版本,就会抛出 NoClassDefFoundError 或 ClassFormatError 的实例,这取决于该类是由系统类加载器还是用户定义的类加载器加载。
· 否则,如果声称的表征实际上并不代表一个名为N的类,加载时将抛出一个NoClassDefFoundError的实例或其子类的一个实例。
\3. 如果C有一个直接的超类,那么使用§5.4.3.1的算法解决从C到其直接超类的符号引用。注意,如果C是一个接口,它必须有Object作为它的直接超类,而Object必须已经被加载。只有Object没有直接的超类。
任何由于类或接口解析而产生的异常都可以在加载的这个阶段抛出。此外,这个阶段的加载必须检测以下错误。
· 如果被命名为C的直接超类的类或接口实际上是一个接口,加载时会抛出一个IncompatibleClassChangeError。
· 否则,如果C的任何一个超类是C本身,加载会抛出一个ClassCircularityError。
\4. 如果C有任何直接的超接口,那么从C到它的直接超接口的符号引用将用§5.4.3.1的算法来解决。
任何由于类或接口解析而产生的异常都可以在加载的这个阶段抛出。此外,这个阶段的加载必须检测以下错误。
· 如果任何一个被命名为C的直接超接口的类或接口实际上不是一个接口,加载时会抛出一个IncompatibleClassChangeError。
· 否则,如果C的任何一个超接口是C本身,加载会抛出一个ClassCircularityError。
\5. Java 虚拟机将 C 标记为有 L 作为其定义类加载器,并记录 L 是 C 的启动加载器(§5.3.4)。
5.4.链接
链接一个类或接口包括验证和准备该类或接口,它的直接超类,它的直接超接口,以及它的元素类型(如果它是一个数组类型),如果必要的话。解决类或接口中的符号引用是链接的一个可选的部分。
该规范允许在链接活动(以及由于递归,加载)发生的时间上有实施的灵活性,但必须保持以下所有的属性。
· 一个类或接口在被链接之前已经完全加载。
· 一个类或接口在被初始化之前是完全被验证和准备的。
· 在链接过程中检测到的错误是在程序中的某个点抛出的,在这个点上,程序采取的某些行动可能直接或间接地需要链接到错误中涉及的类或接口。
例如,Java虚拟机的实现可以选择在使用类或接口时单独解析每个符号引用("懒 "或 "晚 "解析),或者在验证类时一次性解析它们("急 "或 "静态 "解析)。这意味着,在某些实现中,在类或接口被初始化后,解析过程可能会继续。无论采用哪种策略,在解析过程中检测到的任何错误都必须在程序中(直接或间接)使用类或接口的符号引用的地方抛出。
因为链接涉及到新数据结构的分配,它可能会以OutOfMemoryError的方式失败。
5.4.1.验证
验证(§4.10)确保类或接口的二进制表示在结构上是正确的(§4.9)。验证可能导致额外的类和接口被加载(§5.3),但不需要导致它们被验证或准备。
如果一个类或接口的二进制表示不满足§4.9中列出的静态或结构约束,那么必须在程序中导致该类或接口被验证的地方抛出一个VerifyError。
如果 Java 虚拟机验证类或接口的尝试因为抛出 LinkageError(或子类)实例的错误而失败,那么随后验证该类或接口的尝试总是以最初验证尝试所抛出的相同错误而失败。
5.4.2.准备工作
准备工作包括为类或接口创建静态字段,并将这些字段初始化为它们的默认值(§2.3,§2.4)。这不需要执行任何Java虚拟机代码;静态字段的显式初始化器作为初始化的一部分被执行(§5.5),而不是准备。
在准备一个类或接口C的过程中,Java虚拟机也施加了加载约束(§5.3.4)。让 L1 成为 C 的定义加载器。对于在 C 中声明的每个方法 m,如果覆盖(§5.4.5)在超类或超接口 < D, L2 > 中声明的方法,Java 虚拟机会施加下列加载约束。
鉴于m的返回类型是Tr ,m的形式参数类型是Tf1 ,…,Tfn ,那么。
如果Tr 不是一个数组类型,让T0 成为Tr ;否则,让T0 成为Tr 的元素类型(§2.4)。
对于i=1到n:如果Tfi 不是一个数组类型,让Ti 成为Tfi ;否则,让Ti 成为Tfi 的元素类型(§2.4)。
那么TiL1 = TiL2 ,i = 0至n。
此外,如果C实现了C的超接口< I, L3 >中声明的方法m,但C本身没有声明该方法m,那么让< D, L2 >成为C的超类,它声明了C所继承的方法m的实现。Java虚拟机规定了以下约束。
鉴于m的返回类型是Tr ,m的形式参数类型是Tf1 ,…,Tfn ,那么。
如果Tr 不是一个数组类型,让T0 成为Tr ;否则,让T0 成为Tr 的元素类型(§2.4)。
对于i=1到n:如果Tfi 不是一个数组类型,让Ti 成为Tfi ;否则,让Ti 成为Tfi 的元素类型(§2.4)。
那么TiL2 = TiL3 ,i = 0至n。
准备工作可以在创建后的任何时候进行,但必须在初始化前完成。
5.4.3.决议
Java虚拟机指令anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield, and putstatic对运行时常量库进行符号引用。执行这些指令中的任何一条都需要解决其符号引用。
解析是指从运行时常量池的符号引用中动态地确定具体数值的过程。
一个被调用的动态指令的符号参考的解决并不意味着相同的符号参考被认为对任何其他被调用的动态指令的解决。
对于上述所有其他指令,解决一个指令发生的符号参考意味着相同的符号参考被视为解决了任何其他非诱发的动态指令。
(上面的文字意味着通过解析为特定的被调用的动态指令确定的具体值是与该特定被调用的动态指令绑定的调用站点对象)。
可以尝试解析已经被解析过的符号引用。试图解析一个已经被成功解析的符号引用总是很容易成功的,并且总是会产生与该引用的初始解析相同的实体。
如果在解析符号引用时发生错误,那么必须在程序中(直接或间接)使用该符号引用的地方抛出IncompatibleClassChangeError(或一个子类)的实例。
如果Java虚拟机试图解决一个符号引用失败,因为抛出的错误是LinkageError(或一个子类)的实例,那么随后试图解决该引用的尝试总是以最初的解决尝试所抛出的相同错误而失败。
一个特定的被调用的动态指令对调用站点指定器的符号引用在执行该指令之前不能被解决。
在调用的动态指令解析失败的情况下,引导方法在随后的解析尝试中不会被重新执行。
上面的某些指令在解析符号引用时需要额外的链接检查。例如,为了使getfield指令成功地解析它所操作的字段的符号引用,它不仅必须完成§5.4.3.2中给出的字段解析步骤,而且还要检查该字段是否是静态的。如果它是一个静态字段,必须抛出一个链接异常。
值得注意的是,为了使调用的动态指令成功地解决对调用站点指定符的符号引用,其中指定的bootstrap方法必须正常完成并返回一个合适的调用站点对象。如果引导方法突然完成或返回一个不合适的调用站点对象,必须抛出一个链接异常。
由特定的 Java 虚拟机指令的执行的检查所产生的链接异常在该指令的描述中给出,在这个关于解析的一般讨论中没有涉及。请注意,这种异常虽然被描述为 Java 虚拟机指令执行的一部分,而不是解析的一部分,但仍被正确地认为是解析失败的原因。
下面几节描述了解析类或接口D的运行时常量池(§5.1)中的符号引用的过程。解析的细节因要解析的符号引用的种类而不同。
5.4.3.1.类和接口解析
为了解决从D到用N表示的类或接口C的一个未解决的符号引用,要执行以下步骤。
\1. D的定义类加载器被用来创建一个用N表示的类或接口,这个类或接口就是C。这个过程的细节在§5.3中给出。
任何可以因类或接口创建失败而抛出的异常,都可以因类和接口解析失败而抛出。
\2. 如果C是一个数组类,它的元素类型是一个引用类型,那么通过递归调用§5.4.3.1的算法来解决对代表元素类型的类或接口的符号引用。
\3. 最后,检查对C的访问权限。
· 如果C对D不能访问(§5.4.4),类或接口解析会抛出IllegalAccessError。
这种情况可能发生,例如,如果C是一个最初被声明为公共的类,但在D被编译后被改变为非公共的。
如果步骤1和2成功了,但步骤3失败了,C仍然有效,可以使用。尽管如此,解析失败,D被禁止访问C。
5.4.3.2.现场解决
为了解决从 D 到类或接口 C 中的一个字段的未解决的符号引用,必须首先解决由字段引用给出的对 C 的符号引用(§5.4.3.1)。因此,任何可以由于类或接口引用的解析失败而抛出的异常都可以由于字段解析的失败而抛出。如果对C的引用可以被成功地解析,那么可以抛出一个与字段引用本身的解析失败有关的异常。
当解析一个字段引用时,字段解析首先尝试在C和它的超类中查找被引用的字段。
\1. 如果C声明了一个具有字段引用所指定的名称和描述符的字段,那么字段查找就成功了。声明的字段就是字段查找的结果。
\2. 否则,字段查找将递归地应用于指定的类或接口C的直接超接口。
\3. 否则,如果C有一个超类S,字段查找将递归地应用于S。
\4. 否则,字段查询失败。
然后。
· 如果字段查询失败,字段解析会抛出NoSuchFieldError。
· 否则,如果字段查找成功,但被引用的字段对D来说是不可访问的(§5.4.4),字段解析会抛出一个IllegalAccessError。
· 否则,让< E, L1 >是被引用字段实际声明的类或接口,让L2 是D的定义加载器。
考虑到被引用字段的类型是Tf ,如果Tf 不是一个数组类型,就让T是Tf ,否则就让T是Tf 的元素类型(§2.4)。
Java虚拟机必须施加加载约束,即TL1 = TL2 (§5.3.4)。
5.4.3.3.方法解决
为了解决从D到C类中的一个方法的未解决的符号引用,首先要解决由方法引用给出的对C的符号引用(§5.4.3.1)。因此,任何由于类引用的解析失败而产生的异常都可以作为方法解析失败的结果被抛出。如果对C的引用可以被成功解析,那么与方法引用本身的解析有关的异常就可以被抛出。
当解决一个方法引用时。
\1. 如果C是一个接口,方法解析会抛出一个IncompatibleClassChangeError。
\2. 否则,方法解析会试图在C和它的超类中找到被引用的方法。
· 如果C声明正好有一个方法的名称是由方法引用指定的,并且该声明是一个签名多态方法(§2.9),那么方法查找就会成功。描述符中提到的所有类名都被解析(§5.4.3.1)。
解决的方法是签名多态方法声明。C没有必要用方法引用指定的描述符来声明一个方法。
· 否则,如果C声明了一个具有方法引用所指定的名称和描述符的方法,方法查找就会成功。
· 否则,如果C有一个超类,方法解析的第2步将在C的直接超类上递归调用。
\3. 否则,方法解析试图在指定类C的超接口中找到被引用的方法。
· 如果方法引用所指定的名称和描述符的C的最大特定超接口方法正好包括一个没有设置ACC_ABSTRACT标志的方法,那么这个方法被选中,方法查找成功。
· 否则,如果C的任何一个超接口声明了一个方法,其名称和描述符由方法引用指定,既没有设置ACC_PRIVATE标志,也没有设置ACC_STATIC标志,则任意选择其中之一,方法查找成功。
· 否则,方法查询失败。
对于一个特定的方法名称和描述符来说,一个类或接口C的最大特定的超接口方法是指以下所有情况都是真的任何方法。
· 该方法在C的一个超接口(直接或间接)中声明。
· 该方法以指定的名称和描述符被声明。
· 该方法的ACC_PRIVATE标志和ACC_STATIC标志都没有设置。
· 当方法被声明在接口I中时,不存在其他具有指定名称和描述符的C的最大特定超接口方法,该方法被声明在I的一个子接口。
方法解析的结果由方法查找的成功或失败决定。
· 如果方法查询失败,方法解析会抛出NoSuchMethodError。
· 否则,如果方法查找成功,而被引用的方法对D来说是不可访问的(§5.4.4),方法解析会抛出一个IllegalAccessError。
· 否则,让< E, L1 >是被引用的方法m实际被声明的类或接口,让L2 是D的定义加载器。
鉴于m的返回类型是Tr ,m的形式参数类型是Tf1 ,…,Tfn ,那么。
如果Tr 不是一个数组类型,让T0 成为Tr ;否则,让T0 成为Tr 的元素类型(§2.4)。
对于i=1到n:如果Tfi 不是一个数组类型,让Ti 成为Tfi ;否则,让Ti 成为Tfi 的元素类型(§2.4)。
Java虚拟机必须对i=0到n\的加载约束TiL1 = TiL2 (§5.3.4)。
当解析在类的超接口中搜索一个方法时,最好的结果是确定一个最大限度的特定非抽象方法。这个方法有可能会被方法选择所选择,所以最好为它添加类加载器约束。
否则,结果是不确定的。这并不新鲜:《Java®虚拟机规范》从来没有明确选择哪种方法,以及如何打破 “平局”。在Java SE 8之前,这主要是一种无法观察到的区别。然而,从Java SE 8开始,接口方法的集合更加异质化,所以必须注意避免非确定性行为的问题。因此。
· 超界面的方法如果是私有和静态的,就会被解析忽略。这与Java编程语言是一致的,在该语言中这种接口方法是不被继承的。
· 被解决的方法所控制的任何行为都不应该依赖于该方法是否是抽象的。
请注意,如果解析的结果是一个抽象的方法,被引用的类C可能是非抽象的。要求C是抽象的会与超接口方法的非确定性选择相冲突。相反,解析假设被调用对象的运行时类有该方法的具体实现。
5.4.3.4.接口方法解析
为了解决从D到接口C中的一个接口方法的未解决的符号引用,首先要解决由接口方法引用给出的对C的符号引用(§5.4.3.1)。因此,任何由于接口引用的解析失败而产生的异常都可以作为接口方法解析失败的结果被抛出。如果对C的引用可以被成功地解析,那么与接口方法引用本身的解析有关的异常就可以被抛出。
当解析一个接口方法引用时。
- 如果C不是一个接口,接口方法解析会抛出一个IncompatibleClassChangeError。
\2. 否则,如果C声明了一个具有接口方法引用所指定的名称和描述符的方法,方法查找就会成功。
\3. 否则,如果类Object声明了一个名字和描述符由接口方法引用指定的方法,该方法设置了ACC_PUBLIC标志,并且没有设置ACC_STATIC标志,则方法查找成功。
\4. 否则,如果C的最大特定超接口方法(§5.4.3.3)为方法引用所指定的名称和描述符恰好包括一个没有设置ACC_ABSTRACT标志的方法,那么这个方法被选中,方法查找成功。
\5. 否则,如果C的任何一个超接口声明了一个方法,其名称和描述符由方法引用指定,既没有设置ACC_PRIVATE标志,也没有设置ACC_STATIC标志,则任意选择其中之一,方法查找成功。
\6. 否则,方法查询失败。
接口方法解析的结果由方法查找的成功或失败决定。
· 如果方法查询失败,接口方法解析会抛出NoSuchMethodError。
· 如果方法查找成功,而被引用的方法对D来说是不可访问的(§5.4.4),接口方法解析会抛出IllegalAccessError。
· 否则,让< E, L1 >是实际声明了被引用的接口方法m的类或接口,让L2 是D的定义加载器。
鉴于m的返回类型是Tr ,m的形式参数类型是Tf1 ,…,Tfn ,那么。
如果Tr 不是一个数组类型,让T0 成为Tr ;否则,让T0 成为Tr 的元素类型(§2.4)。
对于i=1到n:如果Tfi 不是一个数组类型,让Ti 成为Tfi ;否则,让Ti 成为Tfi 的元素类型(§2.4)。
Java虚拟机必须对i=0到n\的加载约束TiL1 = TiL2 (§5.3.4)。
关于可访问性的条款是必要的,因为接口方法解析可能会选取接口C的一个私有方法。(在Java SE 8之前,接口方法解析的结果可能是类Object的一个非公共方法或类Object的一个静态方法;这种结果与Java编程语言的继承模型不一致,在Java SE 8及以上版本中是不允许的)。
5.4.3.5.方法类型和方法处理决议
为了解决对方法类型的未解决的符号引用,就像解决对类和接口(§5.4.3.1)的未解决的符号引用一样,其名称与方法描述符(§4.3.3)中给出的类型相对应。
任何可以作为类引用解析失败的结果抛出的异常,都可以作为方法类型解析失败的结果抛出。
方法类型解析成功的结果是对代表方法描述符的java.lang.invoke.MethodType实例的一个引用。
无论运行时常量池是否真的包含对方法描述符中所示类和接口的符号引用,方法类型解析都会发生。另外,解析被认为是发生在未解析的符号引用上,因此,如果合适的类和接口可以被后来的时间加载,那么解析一个方法类型的失败不一定会导致解析另一个具有相同文本方法描述符的方法类型的失败。
对一个方法句柄的未解决的符号引用的解决是比较复杂的。每个被Java虚拟机解决的方法句柄都有一个等同的指令序列,称为它的字节码行为,由方法句柄的种类表示。表5.4.3.5-A中给出了九种方法句柄的整数值和描述。
指令序列对字段或方法的符号引用用C.x:T表示,其中x和T是字段或方法的名称和描述符(§4.3.2,§4.3.3),C是要找到该字段或方法的类或接口。
表5.4.3.5-A.方法处理的字节码行为
Kind | 描述 | 解释 |
---|---|---|
1 |
REF_getField
|
getfield C.f:T
|
2 |
REF_getStatic
|
getstatic C.f:T
|
3 |
REF_putField
|
putfield C.f:T
|
4 |
REF_putStatic
|
putstatic C.f:T
|
5 |
REF_invokeVirtual
|
invokevirtual C.m:(A)T
|
6 |
REF_invokeStatic
|
invokestatic C.m:(A)T
|
7 |
REF_invokeSpecial
|
invokespecial C.m:(A)T
|
8 |
REF_newInvokeSpecial
|
new C; dup; invokespecial C.<init>:(A)V
|
9 |
REF_invokeInterface
|
invokeinterface C.m:(A)T
|
让MH是对一个正在解决的方法句柄(§5.1)的符号引用。那么。
· 让R成为MH中包含的字段或方法的符号参考。
(R是从CONSTANT_Fieldref、CONSTANT_Methodref或CONSTANT_InterfaceMethodref结构中派生出来的,该结构是由MH派生出来的CONSTANT_MethodHandle的reference_index项所引用的。)
· 让T是R所引用的字段的类型,或者R所引用的方法的返回类型。让A是R所引用的方法的参数类型序列(也许是空的)。
(T和A是从CONSTANT_Fieldref、CONSTANT_Methodref或CONSTANT_InterfaceMethodref结构中的name_and_type_index项所引用的CONSTANT_NameAndType结构中派生出来的,而R是从该结构中派生出来的)。
为了解决MH,在MH的字节码行为中,所有对类、接口、字段和方法的符号引用都被解决,使用以下三个步骤。
· 首先,R被解决了。
· 其次,解析发生在对类和接口的未解决的符号引用上,这些类和接口的名字依次对应于A中的每个类型和T类型。
· 第三,对 java.lang.invoke.MethodType 实例的引用是通过解析对方法类型的未解析符号引用获得的,该方法类型包含表 5.4.3.5-B 中为 MH 的种类指定的方法描述符。
这就好比对方法句柄的符号引用包含了对方法类型的符号引用,而这个被解析的方法句柄最终将拥有这个方法类型。方法类型的详细结构可以通过检查表5.4.3.5-B获得。
在每个步骤中,任何可以作为类或接口或字段或方法引用解析失败的结果而抛出的异常,都可以作为方法句柄解析失败的结果而抛出。
其目的是,解析方法句柄可以在与Java虚拟机成功解析字节码行为中的符号引用完全相同的情况下完成。特别是,私有和受保护成员的方法句柄可以在那些相应的正常访问合法的类中创建。
表5.4.3.5-B.方法句柄的方法描述符
种类 | 描述 | 方法描述符 |
---|---|---|
1 | REF_getField | ©T |
2 | REF_getStatic | ()T |
3 | REF_putField | (C,T)V |
4 | REF_putStatic | (T)V |
5 | REF_invokeVirtual | (C,A)T |
6 | REF_invokeStatic | (A)T |
7 | REF_invokeSpecial | (C,A)T |
8 | REF_newInvokeSpecial | (A)C |
9 | REF_invokeInterface | (C,A)T |
成功解决方法句柄的结果是对代表方法句柄MH的java.lang.invoke.MethodHandle实例的一个引用。
这个java.lang.invoke.MethodHandle实例的类型描述符是上面方法句柄解析的第三步中产生的java.lang.invoke.MethodType实例。
方法句柄的类型描述符是这样的:在java.lang.invoke.MethodHandle中对该方法句柄的有效调用具有与字节码行为完全相同的堆栈效果。在一个有效的参数集上调用这个方法句柄,其效果与相应的字节码行为完全一样,并返回相同的结果(如果有的话)。
如果R引用的方法设置了ACC_VARARGS标志(§4.6),那么java.lang.invoke.MethodHandle实例是一个可变节律的方法句柄;否则,它是一个固定节律的方法句柄。
当通过invoke调用时,一个可变节数的方法句柄会执行参数列表装箱(JLS §15.12.4.2),而它在invokeExact方面的行为就像没有设置ACC_VARARGS标志一样。
如果R引用的方法设置了ACC_VARARGS标志,并且A\是一个空序列或者A\中的最后一个参数类型不是数组类型,那么方法句柄解析会抛出一个不兼容的ClassChangeError。也就是说,创建一个可变节数的方法句柄失败。
Java虚拟机的实现并不需要对方法类型或方法句柄进行实习。也就是说,两个结构相同的对方法类型或方法句柄的不同符号引用可能不会分别解析为java.lang.invoke.MethodType或java.lang.invoke.MethodHandle的同一实例。
Java SE平台API中的java.lang.invoke.MethodHandles类允许创建没有字节码行为的方法柄。它们的行为由创建它们的java.lang.invoke.MethodHandles方法定义。例如,一个方法句柄在被调用时,可以首先对其参数值进行转换,然后将转换后的值提供给另一个方法句柄的调用,然后对该调用返回的值进行转换,然后将转换后的值作为自己的结果返回。
5.4.3.6.呼叫现场指定人员决议
要解决一个未解决的对调用站点指定器的符号引用包括三个步骤。
· 调用站点指定符给出了对方法句柄的符号引用,该句柄将作为动态调用站点的引导方法(§4.7.23)。方法句柄被解析以获得对java.lang.invoke.MethodHandle实例的引用(§5.4.3.5)。
· 一个调用站点指定器给出了一个方法描述符,TD。对java.lang.invoke.MethodType实例的引用被获得,就像通过解析一个与TD具有相同参数和返回类型的方法类型的符号引用一样(§5.4.3.5)。
· 一个调用站点指定器给出了零个或多个静态参数,这些参数向引导方法传递特定于应用程序的元数据。任何静态参数如果是对类、方法柄或方法类型的符号引用,就像调用ldc指令(§ldc)一样被解析,以分别获得对Class对象、java.lang.invoke.MethodHandle对象和java.lang.invoke.MethodType对象的引用。任何是字符串字面的静态参数都用来获得对String对象的引用。
呼叫站点指定器的解析结果是一个元组,包括:。
· 指对java.lang.invoke.MethodHandle实例的引用。
· 指对java.lang.invoke.MethodType实例的引用。
· 对Class、java.lang.invoke.MethodHandle、java.lang.invoke.MethodType和String实例的引用。
在解析调用站点指定器中的方法句柄的符号参考时,或者解析调用站点指定器中的方法描述符的方法类型的符号参考时,或者解析任何静态参数的符号参考时,任何与方法类型或方法句柄解析有关的异常都可能被抛出(§5.4.3.5)。
5.4.4.访问控制
一个类或接口C对一个类或接口D是可访问的,当且仅当以下任一情况为真。
· C是公开的。
· C和D是同一个运行时包的成员(§5.3)。
当且仅当以下任何一项为真时,一个字段或方法R对一个类或接口D是可访问的。
· R是公开的。
· R是受保护的,并且被声明在一个类C中,而D是C的子类或C本身。此外,如果R不是静态的,那么对R的符号引用必须包含对一个类T的符号引用,这样T要么是D的子类,要么是D的超类,要么是D本身。
· R要么是受保护的,要么是默认访问的(也就是说,既不是公共的,也不是受保护的,也不是私有的),并且是由与D相同的运行时包中的一个类所声明。
· R是私有的,在D中声明。
关于访问控制的讨论忽略了对受保护字段访问或方法调用的目标的相关限制(目标必须是D类或D的子类型)。这个要求是作为验证过程的一部分来检查的(§4.10.1.8);它不是链接时访问控制的一部分。
5.4.5.覆盖
在类C中声明的一个实例方法mC 覆盖了在类A中声明的另一个实例方法mA ,如果mC 与mA 相同,或者以下所有情况都是真的。
· C是A的一个子类。
· mC 的名称和描述符与mA 相同。
· mC 没有标记ACC_PRIVATE。
· 以下情况之一是真实的。
o mA 被标记为ACC_PUBLIC;或被标记为ACC_PROTECTED;或被标记为既不是ACC_PUBLIC也不是ACC_PROTECTED也不是ACC_PRIVATE,并且A与C属于同一个运行时包。
o mC 覆盖一个方法m’(m’与mC 和mA 不同),这样m’就会覆盖mA 。
5.5.初始化
一个类或接口的初始化包括执行其类或接口的初始化方法(§2.9)。
一个类或接口C只能作为结果被初始化。
· 执行引用C的任何一条Java虚拟机指令new、getstatic、putstatic或invokestatic(§new、§getstatic、§putstatic、§invokestatic)。这些指令通过字段引用或方法引用直接或间接地引用一个类或接口。
在执行新指令时,如果被引用的类还没有被初始化,那么它将被初始化。
在执行getstatic、putstatic或invokestatic指令时,如果没有被初始化的话,声明已解决的字段或方法的类或接口将被初始化。
· java.lang.invoke.MethodHandle实例的第一次调用,该实例是方法句柄解析(§5.4.3.5)的结果,其种类为2(REF_getStatic)、4(REF_putStatic)、6(REF_invokeStatic)或8(REF_newInvokeSpecial)。
这意味着当bootstrap方法被调用为invokedynamic指令(§invokedynamic)时,bootstrap方法的类被初始化,作为持续解决调用站点指定符的一部分。
· 在类库中调用某些反射方法(§2.12),例如,在类Class或包java.lang.reflect中。
· 如果C是一个类,则是其一个子类的初始化。
· 如果C是一个声明了非抽象、非静态方法的接口,直接或间接实现C的类的初始化。
· 如果C是一个类,它被指定为Java虚拟机启动时的初始类(§5.2)。
在初始化之前,一个类或接口必须被链接,也就是被验证,被准备,以及被选择性地解析。
因为Java虚拟机是多线程的,初始化一个类或接口需要仔细的同步,因为其他线程可能同时试图初始化同一个类或接口。还有一种可能性是,类或接口的初始化可能被递归地请求,作为该类或接口初始化的一部分。Java虚拟机的实现负责通过使用以下程序来处理同步和递归初始化问题。它假定类对象已经被验证和准备好了,并且类对象包含的状态表明有四种情况之一。
· 该类对象经过验证和准备,但没有被初始化。
· 这个类对象正在被某个特定的线程初始化。
· 这个类对象已经完全初始化,可以使用了。
· 这个类对象处于一个错误的状态,也许是因为试图初始化而失败。
对于每个类或接口C,都有一个独特的初始化锁LC。从C到LC的映射是由Java虚拟机的实现来决定的。例如,LC可以是C的类对象,或与该类对象相关的监视器。那么,初始化C的过程如下。
在C的初始化锁LC上进行同步,这涉及到等待,直到当前线程能够获得LC。
如果C的类对象表明其他线程正在对C进行初始化,那么释放LC并阻塞当前线程,直到被告知正在进行的初始化已经完成,这时重复这个过程。
线程中断状态不受执行初始化程序的影响。
如果C的Class对象显示当前线程正在对C进行初始化,那么这一定是一个递归的初始化请求。释放LC并正常完成。
如果C的Class对象表明C已经被初始化,那么就不需要进一步的操作。释放LC并正常完成。
如果C的Class对象处于一个错误的状态,那么初始化就不可能了。释放LC并抛出一个NoClassDefFoundError。
否则,记录当前线程正在对C的类对象进行初始化的事实,并释放LC。
然后,按照字段在ClassFile结构中出现的顺序,用ConstantValue属性(§4.7.2)中的常量值初始化C的每个最终静态字段。
接下来,如果C是一个类而不是一个接口,并且它的超类还没有被初始化,那么让SC是它的超类,让SI1 ,…,SIn 是C的所有超接口(无论是直接还是间接),它们至少声明了一个非抽象、非静态的方法。对于每个由C直接实现的接口I(按照C的接口数组的顺序),在返回I之前,枚举在I的超级接口上(按照I的接口数组的顺序)进行递归枚举,给出超级接口的顺序。
- 对于列表[ SC, SI1 , …, SIn ]中的每个S,递归地对S执行这整个程序。如果有必要,先验证和准备S。
- 如果S的初始化由于被抛出的异常而突然完成,那么获取LC,将C的类对象标记为错误的,通知所有等待的线程,释放LC,并突然完成,抛出与初始化SC相同的异常。
接下来,通过查询C的定义类加载器,确定C的断言是否被启用。
接下来,执行C的类或接口初始化方法。
如果类或接口初始化方法的执行正常完成,那么获取LC,将C的类对象标记为完全初始化,通知所有等待的线程,释放LC,并正常完成这个过程。
如果E的类不是Error或者它的一个子类,那么创建一个以E为参数的ExceptionInitializerError类的新实例,并在下面的步骤中使用这个对象来代替E。如果因为发生OutOfMemoryError而不能创建一个新的ExceptionInitializerError实例,那么在下面的步骤中使用一个OutOfMemoryError对象来代替E。
获取LC,将C的类对象标记为错误的,通知所有等待的线程,释放LC,并以上一步确定的原因E或其替代物突然完成这个过程。
Java虚拟机的实现可以优化这个过程,当它可以确定类的初始化已经完成时,可以在步骤1中省略锁的获取(以及步骤4/5中的释放),条件是,就Java内存模型而言,如果锁被获取,所有的发生前顺序(JLS §17.4.5)在执行优化时仍然存在。
5.6.绑定本地方法的实现
绑定是一个过程,用 Java 编程语言以外的语言编写的、实现本地方法的函数被集成到 Java 虚拟机中,从而可以被执行。尽管这个过程在传统上被称为链接,但本规范中使用了绑定一词,以避免与Java虚拟机对类或接口的链接相混淆。
5.7.Java虚拟机退出
当某个线程调用类Runtime或类System的exit方法,或类Runtime的halt方法,且exit或halt操作被安全管理器允许时,Java虚拟机就会退出。
此外,JNI(Java Native Interface)规范描述了在使用JNI Invocation API加载和卸载Java虚拟机时的终止。
第六章 Java虚拟机指令集
一条 Java 虚拟机指令包括一个指定要执行的操作的操作码,然后是体现要操作的值的零个或多个操作数。本章详细介绍了每个Java虚拟机指令的格式和它所执行的操作。
6.1.假设。必须 "的含义
每条指令的描述总是在满足第4节(类文件格式)的静态和结构约束的Java虚拟机代码的背景下给出。在单个Java虚拟机指令的描述中,我们经常说明某些情况是 "必须 "或 "不必须 "的。“值2必须是int类型”。第4节(类文件格式)的约束条件保证了所有这样的期望实际上都会得到满足。如果指令描述中的某些约束("必须 "或 “不必须”)在运行时没有得到满足,Java虚拟机的行为就无法定义。
Java虚拟机在链接时使用类文件验证器检查Java虚拟机代码是否满足静态和结构约束(§4.10)。因此,Java虚拟机将只尝试执行来自有效类文件的代码。在链接时执行验证是很有吸引力的,因为只需执行一次检查,大大减少了必须在运行时完成的工作量。其他实施策略也是可能的,只要它们符合《Java语言规范》,Java SE 8版和《Java虚拟机规范》,Java SE 8版。
6.2.保留的操作码
除了本章后面指定的指令的操作码在类文件中使用外(第4节(类文件格式)),还有三个操作码被保留下来供Java虚拟机实现内部使用。如果Java虚拟机的指令集在将来被扩展,这些保留的操作码将保证不会被使用。
两个保留的操作码,编号为254(0xfe)和255(0xff),分别具有记忆符号impdep1和impdep2。这些指令的目的是为在软件和硬件中实现的特定功能提供 "后门 "或陷阱。第三个保留的操作码,编号为202(0xca),具有记忆性的断点,旨在被调试器用来实现断点。
尽管这些操作码已被保留,但它们只能在Java虚拟机的实现中使用。它们不能出现在有效的类文件中。诸如调试器或 JIT 代码生成器(第 2.13 节)等工具可能与已经加载和执行的 Java 虚拟机代码直接交互,可能会遇到这些操作码。如果这些工具遇到任何这些保留指令,它们应该试图表现得优雅一些。
6.3.虚拟机错误
当内部错误或资源限制使其无法实现本章所述的语义时,Java 虚拟机实现会抛出一个对象,该对象是 VirtualMachineError 类的一个子类的实例。本规范无法预测在哪些地方可能会遇到内部错误或资源限制,也没有精确规定何时可以报告这些错误。因此,下面定义的任何 VirtualMachineError 子类都可能在 Java 虚拟机运行期间的任何时候被抛出。
· InternalError。由于实现虚拟机的软件有故障,底层主机系统软件有故障,或者硬件有故障,在Java虚拟机的实现中发生了内部错误。这个错误在检测到时被异步传递(§2.10),并且可能发生在程序的任何时候。
· OutOfMemoryError。Java虚拟机的实现已经耗尽了虚拟或物理内存,而自动存储管理程序无法回收足够的内存来满足创建对象的请求。
· StackOverflowError。Java虚拟机实现已经耗尽了一个线程的堆栈空间,通常是因为该线程由于执行程序中的故障,正在进行无限制的递归调用。
· UnknownError。发生了一个异常或错误,但Java虚拟机实现无法报告实际的异常或错误。
6.4.指令描述的格式
在本章中,Java虚拟机指令由以下形式的条目表示,按字母顺序排列,每个条目在新的一页开始。
助记符
运作
指示的简短描述
格式
mnemonic
operand1
operand2.
表格
记忆法 = 操作码
操作数栈
…, value1, value2 →
…, value3
描述
一个较长的描述,详细说明对操作数堆栈内容或常量池条目的约束,执行的操作,结果的类型等。
链接异常情况
如果执行这条指令可能会抛出任何链接异常,那么它们将按照必须抛出的顺序,在每一行中列出。
运行时异常情况
如果有任何运行时异常可以通过执行指令抛出,那么它们将按照必须抛出的顺序,在一行中列出。
除了为一条指令列出的链接和运行时异常(如果有的话),该指令不得抛出任何运行时异常,除了VirtualMachineError或其子类的实例。
笔记
不是严格意义上的指令说明的一部分的注释,在说明的末尾作为注释留出。
指令格式图中的每个单元代表一个8位字节。该指令的助记符是它的名字。它的操作码是它的数字表示,以十进制和十六进制两种形式给出。在类文件的Java虚拟机代码中,只有数字表示才是实际存在的。
请记住,有一些 "操作数 "是在编译时生成并嵌入到Java虚拟机指令中的,还有一些 "操作数 "是在运行时计算并在操作数栈上提供的。尽管它们来自几个不同的领域,但所有这些操作数都代表同样的东西:正在执行的Java虚拟机指令所要操作的值。通过隐含地从其操作数堆栈中获取许多操作数,而不是在其编译的代码中明确表示为额外的操作数字节、寄存器编号等,Java虚拟机的代码保持紧凑。
有些指令是作为相关指令家族的成员出现的,它们共享一个描述、格式和操作数堆栈图。因此,一个指令家族包括几个操作码和操作码的助记符;只有家族助记符出现在指令格式图中,单独的表格行列出所有成员的助记符和操作码。例如,lconst_指令族的表格行给出了该族中两条指令(lconst_0和lconst_1)的助记符和操作码信息,即
lconst_0 = 9 (0x9)
lconst_1 = 10 (0xa)
在Java虚拟机指令的描述中,指令的执行对当前帧(§2.6.2)的操作数堆栈(§2.6)的影响是用文字表示的,堆栈从左到右增长,每个值单独表示。因此。
…, value1, value2 →
…,结果
显示了一个操作,开始时值2在操作数堆栈的顶部,值1在它的下面。作为指令执行的结果,value1和value2从操作数堆栈中被弹出,并被指令计算的结果值所取代。操作数堆栈的其余部分,用省略号(…)表示,不受指令执行的影响。
long和double类型的值由操作数栈上的一个条目表示。
在《Java®虚拟机规范》第一版中,操作数堆栈中的long和double类型的值在堆栈图中分别由两个条目表示。
6.5.说明
aaload
运作
从阵列中加载引用
格式
aaload
表格
aaload = 50 (0x32)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组件是引用类型的。索引必须是int类型的。arrayref和index都从操作数栈中被弹出。索引处的数组组件中的引用值被检索出来并推到操作数栈中。
运行时异常情况
如果arrayref为空,aaload会抛出一个NullPointerException。
否则,如果index不在arrayref引用的数组的范围内,aload指令会抛出ArrayIndexOutOfBoundsException。
aastore
运作
存储到参考数组中
格式
aastore
表格
aastore = 83 (0x53)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须引用一个数组,其组件是引用类型的。索引必须是int类型的,值必须是引用类型的。arrayref, index, 和value被从操作数栈中弹出。引用值被存储为索引处的数组组件。
在运行时,值的类型必须与arrayref引用的数组组件的类型兼容。具体来说,只有在以下情况下,才允许将一个参考类型S(源)的值赋值给参考类型T(目标)的数组组件。
· 如果S是一个类的类型,那么。
o 如果T是一个类的类型,那么S必须和T是同一个类,或者S必须是T的一个子类。
o 如果T是一个接口类型,那么S必须实现接口T。
· 如果S是一个接口类型,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是与S相同的接口或S的超接口。
· 如果S是一个数组类型,即SC[]类型,也就是一个SC类型的组件数组,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是由数组实现的接口之一(JLS §4.10.3)。
o 如果T是一个TC[]类型的数组,也就是一个TC类型的组件数组,那么下列情况之一必须为真。
§ TC和SC是同一个原始类型。
§ TC和SC是参考类型,通过这些运行时规则,SC类型可以被分配给TC。
运行时异常情况
如果arrayref\为空,aastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,aastore\指令会抛出ArrayIndexOutOfBoundsException。
否则,如果arrayref\不是空的,并且\值\的实际类型与数组组件的实际类型不赋值兼容(JLS §5.2),aastore\会\抛出一个ArrayStoreException。
aconst_null
运作
推空
格式
aconst_null
表格
aconst_null = 1 (0x1)
操作数栈
…→
…, null
描述
将空对象的引用推到操作数栈上。
笔记
Java虚拟机并没有规定null的具体数值。
aload
运作
从本地变量加载引用
格式
负载
索引
表格
aload = 25 (0x19)
操作数栈
…→
…, objectref
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。索引处的局部变量必须包含一个引用。索引处的局部变量中的objectref被推到操作数栈中。
笔记
aload指令不能用于将局部变量中的returnAddress类型的值加载到操作数栈中。这种与store指令(§astore)的不对称性是故意的。
aload操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
aload_
运作
从本地变量加载引用
格式
aload_
表格
aload_0 = 42 (0x2a)
aload_1 = 43 (0x2b)
aload_2 = 44 (0x2c)
aload_3 = 45 (0x2d)
操作数栈
…→
…, objectref
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。< n>处的局部变量必须包含一个引用。< n>处的局部变量中的objectref被推到操作数栈中。
笔记
aload_指令不能用于将局部变量中的returnAddress类型的值加载到操作数栈中。这种与相应的aster_指令(§astore_)的不对称性是故意的。
每条load_指令都与索引为的load相同,只是操作数是隐含的。
anewarray
运作
创建新的参考数组
格式
一个新数组
indexbyte1
indexbyte2
表格
anewarray = 189 (0xbd)
操作数栈
…,计数→
…, arrayref
描述
Count必须是int类型。它被从操作数栈中弹出。count代表要创建的数组的组件数量。无符号的indexbyte1和indexbyte2用于构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个类、数组或接口类型的符号引用。被命名的类、数组或接口类型被解析(§5.4.3.1)。一个包含该类型组件的新数组,长度为count,从垃圾收集的堆中分配,并且这个新数组对象的引用arrayref被推到操作数堆中。新数组的所有组件都被初始化为null,这是引用类型的默认值(§2.4)。
链接异常情况
在解析对类、数组或接口类型的符号引用时,可以抛出§5.4.3.1中记载的任何异常。
运行时异常情况
否则,如果count\小于0,anewarray\指令会抛出一个NegativeArraySizeException。
笔记
anewarray指令用于创建一个对象引用数组的单一维度或多维数组的一部分。
areturn
运作
从方法中返回引用
格式
是转弯
表格
areturn = 176 (0xb0)
操作数栈
…, objectref →
[空]
描述
objectref必须是引用类型,并且必须指向一个类型与当前方法的返回描述符(§4.3.3)所代表的类型赋值兼容(JLS§5.2)的对象。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并且可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。如果没有抛出异常,objectref就会从当前帧的操作数栈中弹出(§2.6),并推到调用者的帧的操作数栈中。当前方法的操作数栈中的任何其他值都被丢弃了。
然后,解释器恢复了调用者的框架,并将控制权返回给调用者。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,则Areturn\会\抛出一个IllegalMonitorStateException。例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上,这种情况可能发生。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法时违反了其中的第一条规则,那么areturn\会\抛出一个IllegalMonitorStateException。
arraylength
运作
获取数组的长度
格式
阵列长度
表格
arraylength = 190 (0xbe)
操作数栈
…, arrayref →
…, 长度
描述
arrayref必须是引用类型,并且必须指代一个数组。它被从操作数栈中弹出。它所引用的数组的长度被确定。这个长度被作为一个int推到操作数栈中。
运行时异常情况
如果arrayref\为空,arraylength\指令会抛出一个NullPointerException。
astore
运作
将引用存储到本地变量中
格式
仓库
指数
表格
astore = 58 (0x3a)
操作数栈
…, objectref →
…
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。在操作数堆栈顶部的objectref必须是returnAddress类型或引用类型。它被从操作数栈中弹出,索引处的局部变量的值被设置为objectref。
笔记
在实现Java编程语言的finally子句时,store指令与returnAddress类型的objectref一起使用(§3.13)。
aload指令(§aload)不能用来从局部变量向操作数堆栈加载returnAddress类型的值。这种与store指令的不对称性是故意的。
astore操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
astore_
运作
将引用存储到本地变量中
格式
astore_
表格
astore_0 = 75 (0x4b)
astore_1 = 76 (0x4c)
astore_2 = 77 (0x4d)
astore_3 = 78 (0x4e)
操作数栈
…, objectref →
…
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。在操作数堆栈顶部的objectref必须是returnAddress类型或引用类型。它被从操作数栈中弹出,< n>处的局部变量的值被设置为objectref。
笔记
在实现Java编程语言的finally条款时,store_指令与returnAddress类型的objectref一起使用(§3.13)。
aload_指令(§aload_)不能用于将局部变量中的returnAddress类型的值加载到操作数栈中。这种与相应的aster_指令的不对称性是故意的。
astore_指令中的每一条都与索引为的aster相同,只是操作数是隐含的。
athrow
运作
抛出异常或错误
格式
抛出
表格
athrow = 191 (0xbf)
操作数栈
…, objectref →
对象ref
描述
objectref必须是引用类型,并且必须引用一个对象,该对象是Throwable类或Throwable的子类的实例。它被从操作数栈中弹出。然后,通过在当前方法(§2.6)中搜索第一个与objectref的类相匹配的异常处理程序来抛出objectref,如§2.10中的算法。
如果找到了与objectref相匹配的异常处理程序,它就包含了旨在处理这个异常的代码的位置。pc寄存器被重置到该位置,当前帧的操作栈被清空,objectref被推回到操作栈上,然后继续执行。
如果在当前帧中没有找到匹配的异常处理程序,该帧将被弹出。如果当前帧代表一个同步方法的调用,在调用该方法时进入或重新进入的监视器被退出,就像执行monitorexit指令一样(§monitorexit)。最后,其调用者的框架被恢复,如果存在这样的框架,并且对象ref被重新抛出。如果没有这样的框架存在,当前线程就会退出。
运行时异常情况
如果objectref\为空,则抛出一个NullPointerException而不是objectref\。
否则,如果Java虚拟机实现没有执行§2.11.10中描述的关于结构化锁定的规则,那么如果当前帧的方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,那么athrow\会\抛出一个IllegalMonitorStateException,而不是之前抛出的对象。例如,如果一个突然完成的同步方法包含一个monitorexit\指令,但没有monitorenter\指令,就会发生这种情况,该方法在该对象上被同步。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法的过程中违反了其中的第一条规则,那么athrow\就会\抛出一个IllegalMonitorStateException,而不是之前抛出的对象。
笔记
athrow指令的操作数堆栈图可能有误导性。如果这个异常的处理程序在当前方法中被匹配,那么athrow指令会丢弃操作栈中的所有值,然后将被抛出的对象推到操作栈中。然而,如果在当前方法中没有匹配的处理程序,并且该异常是在方法调用链的更上游被抛出的,那么处理该异常的方法(如果有的话)的操作栈被清空,objectref被推到该空的操作栈中。所有从抛出异常的方法到处理异常的方法之间的帧都被丢弃,但不包括处理异常的方法。
baload
运作
从数组中加载字节或布尔值
格式
巴罗德
表格
baload = 51 (0x33)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组成部分是字节类型或布尔类型的。索引必须是int类型的。arrayref和index都从操作数栈中弹出。索引处的数组组件中的字节值被检索出来,符号扩展为一个int值,并被推到操作数栈的顶部。
运行时异常情况
如果arrayref\为空,baload\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,baload\指令会抛出ArrayIndexOutOfBoundsException。
笔记
baload指令被用来从字节数组和布尔数组中加载数值。在Oracle的Java虚拟机实现中,布尔数组–也就是T_BOOLEAN类型的数组(§2.2,§newarray)–被实现为8位值的数组。其他实现可以实现打包的布尔数组;必须使用这种实现的baload指令来访问这些数组。
bastore
运作
存储到字节或布尔数组中
格式
玄关
表格
bastore = 84 (0x54)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组成部分是字节类型或布尔类型的。索引和值必须都是int类型的。arrayref、索引和值从操作数栈中弹出。int值被截断为一个字节,并存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,bastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,bastore\指令会抛出ArrayIndexOutOfBoundsException。
笔记
bastore指令用于将数值存储到字节数组和布尔数组中。在Oracle的Java虚拟机实现中,布尔数组–也就是T_BOOLEAN类型的数组(§2.2,§newarray)–被实现为8位值的数组。其他实现可以实现打包的布尔数组;在这种实现中,bastore指令必须能够将布尔值存储到打包的布尔数组中,以及将字节值存储到字节数组中。
bipush
运作
推送字节
格式
绑定
字节
表格
bipush = 16 (0x10)
操作数栈
…→
…,价值
描述
即时字节被符号扩展为一个int值。该值被推到操作数堆栈中。
caload
运作
从数组中加载char
格式
卡路里
表格
caload = 52 (0x34)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型的,并且必须指向一个组件为char类型的数组。索引必须是int类型的。arrayref和index都从操作数栈中弹出。索引处的数组成分被检索出来,并被零扩展为一个int值。这个值被推到操作数栈上。
运行时异常情况
如果arrayref\为空,caload\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,caload\指令会抛出ArrayIndexOutOfBoundsException。
castore
运作
存储到char数组中
格式
卡斯托雷
表格
castore = 85 (0x55)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须引用一个数组,其组成部分是char类型的。索引和值必须都是int类型。arrayref、索引和值从操作数栈中弹出。int值被截断为char,并存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,castore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,castore\指令会抛出ArrayIndexOutOfBoundsException。
checkcast
运作
检查对象是否属于给定类型
格式
检查广播
indexbyte1
indexbyte2
表格
checkcast = 192 (0xc0)
操作数栈
…, objectref →
…, objectref
描述
objectref必须是引用类型的。无符号的indexbyte1和indexbyte2用于构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。索引处的运行时常量池项目必须是对类、数组或接口类型的符号引用。
如果objectref是空的,那么操作数栈是不变的。
否则,命名的类、数组或接口类型被解析(§5.4.3.1)。如果objectref可以被投射到已解析的类、数组或接口类型上,操作数栈就不会改变;否则,checkcast指令会抛出一个ClassCastException。
下面的规则被用来确定一个非空的objectref是否可以被投到已解决的类型中:如果S是objectref所引用的对象的类,而T是已解决的类、数组或接口类型,checkcast确定objectref是否可以被投到类型T,如下所示。
· 如果S是一个普通的(非数组)类,那么。
o 如果T是一个类的类型,那么S必须和T是同一个类,或者S必须是T的一个子类。
o 如果T是一个接口类型,那么S必须实现接口T。
· 如果S是一个接口类型,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是与S相同的接口或S的超接口。
· 如果S是一个代表数组类型SC[]的类,也就是说,一个由SC类型的组件组成的数组,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是由数组实现的接口之一(JLS §4.10.3)。
o 如果T是一个TC[]类型的数组,也就是一个TC类型的组件数组,那么下列情况之一必须为真。
§ TC和SC是同一个原始类型。
§ TC和SC是引用类型,SC类型可以通过这些规则的递归应用被投射到TC。
链接异常情况
在解析对类、数组或接口类型的符号引用时,可以抛出§5.4.3.1中记载的任何异常。
运行时异常
否则,如果objectref\不能被投递到已解决的类、数组或接口类型,checkcast\指令会抛出一个ClassCastException。
笔记
checkcast指令与instanceof指令(§instanceof)非常相似。它的不同之处在于对null的处理,测试失败时的行为(checkcast抛出一个异常,instanceof推送一个结果代码),以及对操作数栈的影响。
d2f
运作
将双数转换为浮点数
格式
d2f
表格
d2f = 144 (0x90)
操作数栈
…,值→
…,结果
描述
在操作数堆栈顶部的值必须是双倍类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),得到值’。然后,value’使用IEEE 754四舍五入模式转换为浮点数结果。这个结果被推到操作数栈中。
当d2f指令是FP-strict(§2.8.2)时,转换的结果总是被四舍五入到浮动值组中最接近的可表示值(§2.3.2)。
当d2f指令不是FP-strict指令时,转换的结果可以从float-extended-exponent值集中取出(§2.3.2);它不一定被四舍五入到float值集中最接近的可表示值。
一个太小而不能用浮点数表示的有限值被转换成相同符号的零;一个太大而不能用浮点数表示的有限值被转换成相同符号的无穷大。一个双倍的NaN被转换为一个浮点数NaN。
笔记
d2f指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息’,也可能会丢失精度。
d2i
运作
将双数转换为英数
格式
d2i
表格
d2i = 142 (0x8e)
操作数栈
…,值→
…,结果
描述
在操作数堆栈顶部的值必须是双倍类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value’。然后value’被转换为一个int。结果被推到操作数栈上。
· 如果值’是NaN,转换的结果是一个int 0。
· 否则,如果该值’不是无穷大,它将被四舍五入为一个整数值V,使用IEEE 754四舍五入模式向零舍入。如果这个整数值V可以被表示为一个int,那么结果就是int值V。
· 否则,要么值’必须太小(一个幅度很大的负值或负无穷大),而结果是int类型的最小可表示值,要么值’必须太大(一个幅度很大的正值或正无穷大),而结果是int类型的最大可表示值。
笔记
d2i指令执行了一个缩小的原始转换(JLS§5.1.3)。它可能会丢失关于值的整体大小的信息’,也可能丢失精度。
d2l
运作
将双数转换为长数
格式
d2l
表格
d2l = 143 (0x8f)
操作数栈
…,值→
…,结果
描述
在操作数堆栈顶部的值必须是双倍类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value’。然后value’被转换为long。结果被推到操作数栈上。
· 如果值’是NaN,则转换的结果是一个长0。
· 否则,如果该值’不是一个无穷大,它将被四舍五入为一个整数值V,使用IEEE 754四舍五入模式向零舍入。如果这个整数值V可以被表示为长,那么结果就是长值V。
· 否则,要么值’必须太小(一个幅度很大的负值或负无穷大),而结果是最小的长型可表示值,要么值’必须太大(一个幅度很大的正值或正无穷大),而结果是最大的长型可表示值。
笔记
d2l指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息’,也可能会丢失精度。
dadd
运作
增加双倍
格式
父辈
表格
dadd = 99 (0x63)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。双重结果是value1’+ value2’。这个结果被推到操作数栈中。
dadd指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 两个相反符号的无穷大数之和为NaN。
· 两个相同符号的无穷大之和就是该符号的无穷大。
· 一个无穷大和任何有限的值的总和等于无穷大。
· 两个符号相反的零之和为正零。
· 同一符号的两个零之和就是该符号的零。
· 一个零和一个非零的有限值之和等于非零值。
· 两个大小相同、符号相反的非零有限值之和为正零。
· 在其余情况下,如果两个操作数都不是无穷大、零或NaN,并且数值具有相同的符号或不同的大小,则计算和并使用IEEE 754四舍五入模式将其四舍五入到最接近的可表示值。如果幅度太大,不能用双数表示,我们就说操作溢出;结果是一个适当符号的无穷大。如果幅度太小,不能用双倍数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行dadd指令时绝不会抛出运行时异常。
daload
运作
从数组中加载双数
格式
daload
表格
daload = 49 (0x31)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型,并且必须指向一个数组,该数组的组成部分是double类型。索引必须是int类型的。arrayref和index都从操作数栈中被弹出。在索引处的数组分量中的双倍值被检索出来并推到操作数栈中。
运行时异常情况
如果arrayref\为空,daload\将抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,daload\指令会抛出ArrayIndexOutOfBoundsException。
dastore
运作
存储到双数组中
格式
dastore
表格
dastore = 82 (0x52)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须引用一个数组,其组成部分是双倍类型的。索引必须是int类型的,值必须是双倍类型的。arrayref, index, 和value从操作数堆栈中弹出。双重值经过值集转换(§2.8.3),得到value’,它被存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,dastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,dastore\指令会抛出ArrayIndexOutOfBoundsException。
dcmp
运作
对比双倍
格式
dcmp
表格
dcmpg = 152 (0x98)
dcmpl = 151 (0x97)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。进行浮点比较。
· 如果value1’大于value2’,int值1被推到操作数堆栈中。
· 否则,如果value1’等于value2’,int值0被推到操作数堆栈中。
· 否则,如果value1’小于value2’,int值-1被推到操作数栈上。
· 否则,value1’或value2’中至少有一个是NaN。dcmpg指令将int值1推到操作数堆栈,dcmpl指令将int值-1推到操作数堆栈。
浮点比较是按照IEEE 754标准进行的。除NaN以外的所有数值都是有序的,负无穷小于所有有限的数值,正无穷大于所有有限的数值。正零和负零被认为是相等的。
笔记
dcmpg和dcmpl指令的区别仅在于它们对涉及NaN的比较的处理。NaN是无序的,所以如果任何一个或两个操作数都是NaN,那么任何双倍比较都会失败。有了dcmpg和dcmpl,任何双倍比较都可以被编译为将相同的结果推到操作数堆栈中,不管是在非NaN值上比较失败还是因为遇到NaN而失败。更多信息请参见第3.5节。
dconst_
运作
推双倍
格式
dconst_
表格
dconst_0 = 14 (0xe)
dconst_1 = 15 (0xf)
操作数栈
…→
…, < d>
描述
将双常数< d>(0.0或1.0)推到操作数堆栈中。
ddiv\
运作
除以双数
格式
ddiv
表格
ddiv = 111 (0x6f)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。双重结果是value1’/ value2’。这个结果被推到操作数栈中。
ddiv指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,如果两个值的符号相同,结果的符号是正的,如果两个值的符号不同,则是负的。
· 用无穷大除以无穷大的结果是NaN。
· 用一个有限的值除以一个无穷大,结果是一个有符号的无穷大,刚才给出的符号产生规则。
· 用一个有限的值除以一个无限的值,结果是一个有符号的零,刚才给出的符号产生规则。
· 0除以0的结果是NaN;0除以任何其他有限值的结果是一个有符号的0,用刚才给出的符号产生规则。
· 用一个非零的有限值除以一个零,会得到一个有符号的无穷大,刚才给出的符号产生规则。
· 在其余情况下,如果两个操作数都不是无穷大、零或NaN,则计算商并使用IEEE 754四舍五入模式将其四舍五入到最接近的双数。如果幅度太大,无法表示为双数,我们就说操作溢出了;结果是一个适当符号的无穷大。如果幅度太小,不能用双数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢、除以零或精度损失,但执行ddiv指令永远不会抛出一个运行时异常。
dload
运作
从本地变量加载双数
格式
负载
索引
表格
dload = 24 (0x18)
操作数栈
…→
…,价值
描述
索引是一个无符号的字节。index和index+1都必须是当前帧的局部变量阵列的索引(§2.6)。索引处的局部变量必须包含一个双数。索引处的局部变量的值被推到操作数栈上。
笔记
dload操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
dload_
运作
从本地变量加载双数
格式
dload_<n
表格
dload_0 = 38 (0x26)
dload_1 = 39 (0x27)
dload_2 = 40 (0x28)
dload_3 = 41 (0x29)
操作数栈
…→
…,价值
描述
< n>和< n>+1都必须是当前帧的局部变量阵列的索引(§2.6)。< n>处的局部变量必须包含一个双数。< n>处的局部变量的值被推到操作数栈上。
笔记
每条dload_指令都与索引为的dload相同,只是操作数是隐含的。
dmul\
运作
乘以双数
格式
dmul
表格
dmul = 107 (0x6b)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。双重结果是value1’ value2’。这个结果被推到操作数栈中。
dmul指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,如果两个值的符号相同,那么结果的符号是正的,如果两个值的符号不同,则是负的。
· 一个无穷大的数字与一个零相乘的结果是NaN。
· 用一个无穷大的值乘以一个有限的值,会得到一个有符号的无穷大,这就是刚才给出的符号产生规则。
· 在其余情况下,如果既不涉及无穷大也不涉及NaN,则计算乘积并使用IEEE 754四舍五入模式四舍五入到最接近的可表示值。如果量级太大,不能以双数表示,我们就说操作溢出了;结果是一个适当符号的无穷大。如果幅度太小,不能用双倍数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行dmul指令永远不会抛出一个运行时异常。
dneg
运作
否定双倍
格式
dneg
表格
dneg = 119 (0x77)
操作数栈
…,值→
…,结果
描述
该值必须是双倍的类型。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),结果为值’。双倍的结果是值的算术否定。这个结果被推到操作数栈中。
对于双倍值,否定不等于从零减去。如果x是+0.0,那么0.0-x等于+0.0,但-x等于-0.0。单元减法只是颠倒了双数的符号。
值得关注的特殊情况。
· 如果操作数是NaN,结果就是NaN(记得NaN没有符号)。
· 如果操作数是一个无穷大,结果是相反符号的无穷大。
· 如果操作数是零,结果是相反符号的零。
drem
运作
余下的双倍
格式
姆
表格
drem = 115 (0x73)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。结果被计算出来并作为一个双数被推到操作数栈中。
drem指令的结果与IEEE 754定义的所谓余数运算的结果不一样。IEEE 754的 "余数 "操作计算的是四舍五入除法的余数,而不是截断除法,因此它的行为与通常的整数余数运算符的行为不相类似。相反,Java虚拟机定义的drem的行为类似于Java虚拟机的整数余数指令(irem和lrem);这可以与C库函数fmod相比较。
drem指令的结果受这些规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,那么结果的符号就等于红利的符号。
· 如果除数是无穷大,或者除数是零,或者两者都是,结果是NaN。
· 如果分红是有限的,除数是无限的,那么结果就等于分红。
· 如果分红是零,除数是有限的,结果等于分红。
· 在其余情况下,如果操作数都不是无穷大、零或NaN,那么红利值1’和除数值2’的浮点余数结果是由数学关系result = value1’ - (value2’ q)定义的,其中q是一个整数,只有当value1’/ value2’为负数时才是负数,只有当value1’/ value2’为正数时才是正数,其大小尽可能不超过value1’ 和 value2’的真实数学商的大小。
尽管可能会发生除以0的情况,但对drem指令的评估绝不会抛出运行时异常。溢出、下溢或精度损失都不会发生。
笔记
IEEE754的余数运算可以通过库例程Math.IEEEremainder来计算。
dreturn
运作
从方法中返回双数
格式
归来
表格
dreturn = 175 (0xaf)
操作数栈
…,值→
[空]
描述
当前方法的返回类型必须是double。值必须是双倍的类型。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。如果没有抛出异常,值将从当前帧的操作数堆栈中弹出(§2.6),并进行值集转换(§2.8.3),得到值’。值’被推到调用者所在帧的操作数栈中。当前方法的操作数栈中的任何其他值都被丢弃。
然后解释器将控制权返回给方法的调用者,恢复调用者的框架。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,dreturn\会\抛出一个IllegalMonitorStateException。例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上,这种情况可能发生。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法时违反了其中的第一条规则,那么dreturn\会抛\出一个IllegalMonitorStateException。
dstore
运作
将双数存储到本地变量中
格式
仓库
索引
表格
dstore = 57 (0x39)
操作数栈
…,值→
…
描述
索引是一个无符号的字节。index和index+1都必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是双倍类型。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),结果是值’。位于index和index+1的局部变量被设置为value’。
笔记
dstore操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
dstore_
运作
将双数存储到本地变量中
格式
dstore_
表格
dstore_0 = 71 (0x47)
dstore_1 = 72 (0x48)
dstore_2 = 73 (0x49)
dstore_3 = 74 (0x4a)
操作数栈
…,值→
…
描述
< n>和< n>+1都必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是双倍类型。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),结果是值’。位于< n>和< n>+1的局部变量被设置为value’。
笔记
每条dstore_指令都与索引为的dstore相同,只是操作数是隐含的。
dsub
运作
减去双倍
格式
dsub
表格
dsub = 103 (0x67)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是double类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。双重结果是value1’-value2’。这个结果被推到操作数栈中。
对于双倍减法,总是a-b产生与a+(-b)相同的结果。然而,对于dsub指令,从0减去的结果与否定的结果不一样,因为如果x是+0.0,那么0.0-x等于+0.0,但-x等于-0.0。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行dsub指令永远不会抛出一个运行时异常。
dsub
运作
重复顶部操作数的堆栈值
格式
dsub
表格
dup = 89 (0x59)
操作数栈
…,值→
…,价值,价值
描述
复制操作数栈上的最高值,并将复制的值推到操作数栈上。
除非value是第一类计算类型的值,否则不得使用dup指令(§2.11.1)。
dup_x1
运作
复制顶端操作数的堆栈值并向下插入两个值
格式
dup_x1
表格
dup_x1 = 90 (0x5a)
操作数栈
…, value2, value1 →
…,值1,值2,值1
描述
复制操作数堆栈中的最高值,并将复制的值插入操作数堆栈中的两个值。
除非value1和value2都是第1类计算类型的值,否则不得使用dup_x1指令(§2.11.1)。
dup_x2
运作
复制顶端操作数堆栈的值,并向下插入两个或三个值
格式
dup_x2
表格
dup_x2 = 91 (0x5b)
操作数栈
表格1。
…,值3,值2,值1 →
…,值1,值3,值2,值1
其中value1、value2和value3都是第1类计算类型的值(§2.11.1)。
表格2。
…, value2, value1 →
…,值1,值2,值1
其中value1是第一类计算类型的值,value2是第二类计算类型的值(§2.11.1)。
描述
复制操作数堆栈中的最高值,并在操作数堆栈中向下插入复制的值,即两到三个值。
dup2
运作
复制顶部的一个或两个操作数的堆栈值
格式
牯牛降2
表格
dup2 = 92 (0x5c)
操作数栈
表格1。
…, value2, value1 →
…, value2, value1, value2, value1
其中value1和value2都是第一类计算类型的值(§2.11.1)。
表格2。
…,值→
…,价值,价值
其中value是第2类计算类型的一个值(§2.11.1)。
描述
复制操作数堆栈中最上面的一个或两个值,并将复制的值按原来的顺序推回操作数堆栈中。
dup2_x1
运作
复制最上面的一个或两个操作数的堆栈值,向下插入两个或三个值
格式
dup2_x1
表格
dup2_x1 = 93 (0x5d)
操作数栈
表格1。
…,值3,值2,值1 →
…, value2, value1, value3, value2, value1
其中value1、value2和value3都是第1类计算类型的值(§2.11.1)。
表格2。
…, value2, value1 →
…,值1,值2,值1
其中value1是一个第二类计算类型的值,value2是一个第一类计算类型的值(§2.11.1)。
描述
复制操作数堆栈中最上面的一个或两个值,并按原来的顺序在操作数堆栈中原有的一个或多个值下面插入复制的值。
dup2_x2
运作
复制最上面的一个或两个操作数的堆栈值,向下插入两个、三个或四个值
格式
dup2_x2
表格
dup2_x2 = 94 (0x5e)
操作数栈
表格1。
…,值4,值3,值2,值1 →
…, value2, value1, value4, value3, value2, value1
其中value1、value2、value3和value4都是第1类计算类型的值(§2.11.1)。
表格2。
…,值3,值2,值1 →
…,值1,值3,值2,值1
其中value1是第2类计算类型的值,value2和value3都是第1类计算类型的值(§2.11.1)。
表格3。
…,值3,值2,值1 →
…, value2, value1, value3, value2, value1
其中value1和value2都是第一类计算类型的值,value3是第二类计算类型的值(§2.11.1)。
表格4。
…, value2, value1 →
…,值1,值2,值1
其中value1和value2都是第二类计算类型的值(§2.11.1)。
描述
复制操作数堆栈中最上面的一个或两个值,并将复制的值按原来的顺序插入操作数堆栈中。
f2d
运作
将浮点数转换为双数
格式
f2d
表格
f2d = 141 (0x8d)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是float类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),产生值’。然后value’被转换为一个双倍的结果。这个结果被推到操作数栈上。
笔记
当f2d指令是FP-strict(§2.8.2)时,它执行了一个拓宽的原始转换(JLS§5.1.2)。因为浮点值集(§2.3.2)的所有值都可以用双倍值集(§2.3.2)的值精确表示,这样的转换是精确的。
当f2d指令不受FP限制时,转换的结果可以从双倍扩展指数值集中获取;它不一定被四舍五入到双倍值集中最接近的可表示值。然而,如果操作数来自浮动扩展指数值集,而目标结果被限制在双倍值集,则可能需要对值进行四舍五入。
f2i
运作
将浮点数转换为英数
格式
f2i
表格
f2i = 139 (0x8b)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是float类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),产生值’。然后value’被转换为一个int结果。这个结果被推到操作数栈上。
· 如果值’是NaN,转换的结果是一个int 0。
· 否则,如果该值’不是无穷大,它将被四舍五入为一个整数值V,使用IEEE 754四舍五入模式向零舍入。如果这个整数值V可以被表示为一个int,那么结果就是int值V。
· 否则,要么值’必须太小(一个幅度很大的负值或负无穷大),而结果是int类型的最小可表示值,要么值’必须太大(一个幅度很大的正值或正无穷大),而结果是int类型的最大可表示值。
笔记
f2i指令执行了一个缩小的原始转换(JLS§5.1.3)。它可能会丢失关于值的整体大小的信息’,也可能会丢失精度。
f2l
运作
将浮点数转换为长数
格式
f2l
表格
f2l = 140 (0x8c)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是float类型的。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),结果是value’。然后value’被转换为一个长的结果。这个结果被推到操作数栈上。
· 如果值’是NaN,则转换的结果是一个长0。
· 否则,如果该值’不是一个无穷大,它将被四舍五入为一个整数值V,使用IEEE 754四舍五入模式向零舍入。如果这个整数值V可以被表示为长,那么结果就是长值V。
· 否则,要么值’必须太小(一个幅度很大的负值或负无穷大),而结果是最小的长型可表示值,要么值’必须太大(一个幅度很大的正值或正无穷大),而结果是最大的长型可表示值。
笔记
f2l指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息’,也可能会丢失精度。
fadd
运作
添加浮动
格式
桨叶
表格
fadd = 98 (0x62)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。浮点数的结果是value1’+ value2’。这个结果被推到操作数栈中。
fadd指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 两个相反符号的无穷大数之和为NaN。
· 两个相同符号的无穷大之和就是该符号的无穷大。
· 一个无穷大和任何有限的值的总和等于无穷大。
· 两个符号相反的零之和为正零。
· 同一符号的两个零之和就是该符号的零。
· 一个零和一个非零的有限值之和等于非零值。
· 两个大小相同、符号相反的非零有限值之和为正零。
· 在其余情况下,如果两个操作数都不是无穷大、零或NaN,并且数值具有相同的符号或不同的大小,则计算总和并使用IEEE 754四舍五入模式将其四舍五入到最接近的可表示值。如果幅度太大,无法用浮点数表示,我们就说操作溢出了;结果是一个适当符号的无穷大。如果幅度太小,无法用浮点数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行fadd指令时绝不会抛出运行时异常。
faload
运作
从数组中加载浮点数
格式
faload
表格
faload = 48 (0x30)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组成部分是浮点类型的。索引必须是int类型的。arrayref和index都从操作数栈中被弹出。在索引处的数组分量中的浮点值被检索出来并推到操作数栈中。
运行时异常情况
如果arrayref\为空,faload\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,faload\指令会抛出ArrayIndexOutOfBoundsException。
fastore
运作
存储到浮动数组中
格式
fastore
表格
fastore = 81 (0x51)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须引用一个数组,其组成部分是float类型的。索引必须是int类型的,而值必须是float类型的。arrayref、索引和值从操作数栈中弹出。浮点数经过值集转换(§2.8.3),得到value’,value’被存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,fastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,fastore\指令会抛出ArrayIndexOutOfBoundsException。
fcmp
运作
比较浮动
格式
fcmp
表格
fcmpg = 150 (0x96)
fcmpl = 149 (0x95)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。进行浮点比较。
· 如果value1’大于value2’,int值1被推到操作数堆栈中。
· 否则,如果value1’等于value2’,int值0被推到操作数堆栈中。
· 否则,如果value1’小于value2’,int值-1被推到操作数栈上。
· 否则,value1’或value2’中至少有一个是NaN。fcmpg指令将int值1推入操作栈,fcmpl指令将int值-1推入操作栈。
浮点比较是按照IEEE 754标准进行的。除NaN以外的所有数值都是有序的,负无穷小于所有有限的数值,正无穷大于所有有限的数值。正零和负零被认为是相等的。
笔记
fcmpg和fcmpl指令的区别仅在于它们对涉及NaN的比较的处理。NaN是无序的,所以如果任何浮动比较的操作数都是NaN,那么就会失败。有了fcmpg和fcmpl,任何浮动比较都可以被编译为将相同的结果推到操作数堆栈中,无论比较是在非NaN值上失败还是因为遇到NaN而失败。更多信息请参见第3.5节。
fconst_
运作
推举浮动
格式
fconst_<f
表格
fconst_0 = 11 (0xb)
fconst_1 = 12 (0xc)
fconst_2 = 13 (0xd)
操作数栈
…→
…, < f>
描述
将浮点常数< f>(0.0,1.0,或2.0)推到操作数堆栈中。
fdiv
运作
除去浮点数
格式
fdiv
表格
fdiv = 110 (0x6e)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。浮点数的结果是value1’/ value2’。这个结果被推到操作数栈中。
fdiv指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,如果两个值的符号相同,结果的符号是正的,如果两个值的符号不同,则是负的。
· 用无穷大除以无穷大的结果是NaN。
· 用一个有限的值除以一个无穷大,结果是一个有符号的无穷大,刚才给出的符号产生规则。
· 用一个有限的值除以一个无限的值,结果是一个有符号的零,刚才给出的符号产生规则。
· 0除以0的结果是NaN;0除以任何其他有限值的结果是一个有符号的0,用刚才给出的符号产生规则。
· 用一个非零的有限值除以一个零,会得到一个有符号的无穷大,这就是刚才给出的符号产生规则。
· 在其余情况下,如果操作数都不是无穷大、零或NaN,则计算商并使用IEEE 754四舍五入模式将其四舍五入到最近的浮点数。如果幅度太大,不能用浮点数表示,我们就说操作溢出了;结果是一个适当符号的无穷大。如果幅度太小,无法用浮点数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢、除以零或精度损失,但执行fdiv指令永远不会抛出一个运行时异常。
fload
运作
从本地变量中加载浮点数
格式
负载
索引
表格
fload = 23 (0x17)
操作数栈
…→
…,价值
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。索引处的局部变量必须包含一个浮点数。索引处的局部变量的值被推到操作数栈上。
笔记
fload操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
fload_
运作
从本地变量中加载浮点数
格式
fload_
表格
fload_0 = 34 (0x22)
fload_1 = 35 (0x23)
fload_2 = 36 (0x24)
fload_3 = 37 (0x25)
操作数栈
…→
…,价值
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。< n>处的局部变量必须包含一个浮点数。< n>处的局部变量的值被推到操作数栈上。
笔记
每条fload_指令都与索引为的fload相同,只是操作数是隐含的。
fmul
运作
乘以浮点数
格式
fmul
表格
fmul = 106 (0x6a)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。浮点数的结果是value1’ value2’。这个结果被推到操作数栈中。
fmul指令的结果受IEEE算术规则的制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,如果两个值的符号相同,那么结果的符号是正的,如果两个值的符号不同,则是负的。
· 一个无穷大的数字与一个零相乘的结果是NaN。
· 用一个无穷大的值乘以一个有限的值,会得到一个有符号的无穷大,这就是刚才给出的符号产生规则。
· 在其余情况下,如果既不涉及无穷大也不涉及NaN,则计算乘积并使用IEEE 754四舍五入模式将其四舍五入到最接近的可表示值。如果量级太大,无法用浮点数表示,我们就说操作溢出;结果是一个适当符号的无穷大。如果幅度太小,无法用浮点数表示,我们就说操作溢出;结果是一个有适当符号的零。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行fmul指令永远不会抛出一个运行时异常。
fneg
运作
负数浮动
格式
fneg
表格
fneg = 118 (0x76)
操作数栈
…,值→
…,结果
描述
该值必须是浮点类型。它被从操作数堆栈中弹出并进行值集转换(§2.8.3),结果为值’。浮点数的结果是value’的算术否定。这个结果被推到操作数栈中。
对于浮动值,否定不等于从零减去。如果x是+0.0,那么0.0-x等于+0.0,但-x等于-0.0。单元减法只是颠倒了浮点数的符号。
值得关注的特殊情况。
· 如果操作数是NaN,结果就是NaN(记得NaN没有符号)。
· 如果操作数是一个无穷大,结果是相反符号的无穷大。
· 如果操作数是零,结果是相反符号的零。
frem
运作
剩余的浮动部分
格式
自由人
表格
frem = 114 (0x72)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。结果被计算出来并作为一个浮点数推到操作数堆栈中。
frem指令的结果与IEEE754定义的所谓余数运算的结果不一样。IEEE 754的 "余数 "操作计算的是四舍五入除法的余数,而不是截断除法,因此它的行为与通常的整数余数运算符的行为不相类似。相反,Java虚拟机定义的frem的行为类似于Java虚拟机的整数余数指令(irem和lrem);这可以与C库函数fmod相比较。
frem指令的结果受这些规则制约。
· 如果value1’或value2’是NaN,结果是NaN。
· 如果value1’和value2’都不是NaN,那么结果的符号就等于红利的符号。
· 如果除数是无穷大,或者除数是零,或者两者都是,结果是NaN。
· 如果分红是有限的,除数是无限的,那么结果就等于分红。
· 如果分红是零,除数是有限的,结果等于分红。
· 在其余情况下,如果操作数都不是无穷大、零或NaN,那么红利值1’和除数值2’的浮点余数结果是由数学关系result = value1’ - (value2’ q)定义的,其中q是一个整数,只有当value1’/ value2’为负数时才是负数,只有当value1’/ value2’为正数时才是正数,其大小尽可能不超过value1’ 和 value2’的真正数学商数大小。
尽管可能会发生除以0的情况,但对frem指令的评估永远不会抛出运行时异常。溢出、下溢或精度损失不会发生。
笔记
IEEE754的余数运算可以通过库例程Math.IEEEremainder来计算。
freturn
运作
从方法中返回float
格式
freturn
表格
转折 = 174 (0xae)
操作数栈
…,值→
[空]
描述
当前方法的返回类型必须是float。值必须是float类型。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。如果没有抛出异常,值将从当前帧的操作数堆栈中弹出(§2.6),并进行值集转换(§2.8.3),得到值’。值’被推到调用者所在帧的操作数栈中。当前方法的操作数栈中的任何其他值都被丢弃。
然后解释器将控制权返回给方法的调用者,恢复调用者的框架。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,freturn\会\抛出一个IllegalMonitorStateException。例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上,这种情况可能发生。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法时违反了其中的第一条规则,那么freturn\会抛出一个IllegalMonitorStateException。
fstore
运作
将浮动数据存储到本地变量中
格式
基金会
索引
表格
fstore = 56 (0x38)
操作数栈
…,值→
…
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。操作数堆栈顶部的值必须是浮点数类型。它被从操作数栈中弹出并进行值集转换(§2.8.3),结果是值’。索引处的局部变量的值被设置为value’。
笔记
fstore操作码可以和wide指令(§wide)一起使用,用一个两字节的无符号索引访问一个局部变量。
fstore_
运作
将浮动数据存储到本地变量中
格式
fstore_
表格
fstore_0 = 67 (0x43)
fstore_1 = 68 (0x44)
fstore_2 = 69 (0x45)
fstore_3 = 70 (0x46)
操作数栈
…,值→
…
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是float类型的。它被从操作数栈中弹出并进行值集转换(§2.8.3),结果是值’。位于< n>的局部变量的值被设置为value’。
笔记
每条fstore_指令都与索引为的fstore相同,只是操作数是隐含的。
fsub\
运作
减去浮点数
格式
fsub
表格
fsub = 102 (0x66)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是float类型。这些值从操作数堆栈中弹出并进行值集转换(§2.8.3),得到value1’和value2’。浮点数的结果是value1’-value2’。这个结果被推到操作数栈中。
对于浮点数减法,总是a-b产生与a+(-b)相同的结果。然而,对于fsub指令,从0减去的结果与否定的结果不同,因为如果x是+0.0,那么0.0-x等于+0.0,但-x等于-0.0。
Java虚拟机要求支持IEEE 754所定义的渐进下溢。尽管可能会发生溢出、下溢或精度损失,但执行fsub指令永远不会抛出一个运行时异常。
getfield
运作
从对象中获取字段
格式
场地
indexbyte1
indexbyte2
表格
getfield = 180 (0xb4)
操作数栈
…, objectref →
…,价值
描述
objectref,必须是引用类型的,从操作数栈中弹出。无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个字段的符号引用(§5.1),它给出了字段的名称和描述符,以及对要找到该字段的类的符号引用。被引用的字段被解析(§5.4.3.2)。objectref中被引用字段的值被获取并推到操作数堆栈中。
objectref的类型不能是一个数组类型。如果这个字段是受保护的,并且它是当前类的超类的成员,并且这个字段不是在与当前类相同的运行时包(§5.3)中声明的,那么 objectref 的类必须是当前类或当前类的子类。
链接异常情况
在解析对字段的符号引用时,可以抛出任何与字段解析有关的错误(§5.4.3.2)。
否则,如果解决的字段是一个静态字段,getfield\会抛出一个IncompatibleClassChangeError。
运行时异常
否则,如果objectref\为空,getfield\指令会抛出一个NullPointerException。
笔记
getfield指令不能用来访问一个数组的长度字段。应使用arraylength指令(§arraylength)来代替。
getstatic
运作
从类中获取静态字段
格式
获得静态
indexbyte1
indexbyte2
表格
getstatic = 178 (0xb2)
操作数栈
…, →
…,价值
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个字段的符号引用(§5.1),它给出了字段的名称和描述符,以及对要找到该字段的类或接口的符号引用。被引用的字段被解析(§5.4.3.2)。
在成功解析字段时,如果声明被解析字段的类或接口尚未被初始化,那么该类或接口将被初始化(§5.5)。
类或接口字段的值被获取并推送到操作数堆栈。
链接异常情况
在解析类或接口字段的符号引用时,可以抛出任何与字段解析有关的异常(§5.4.3.2)。
否则,如果解决的字段不是静态(类)字段或接口字段,getstatic\会抛出\一个IncompatibleClassChangeError。
运行时异常
否则,如果这个getstatic\指令的执行导致被引用的类或接口的初始化,getstatic\可能会抛出一个Error,详见§5.5。
goto
运作
分部总是
格式
转到
branchbyte1
branchbyte2
表格
goto = 167 (0xa7)
操作数栈
无变化
描述
无符号字节 branchbyte1 和 branchbyte2 被用来构建一个有符号的16位分支偏移,其中 branchoffset 是 (branchbyte1 << 8) | branchbyte2。从这条goto指令的操作码的地址开始,在该偏移处执行。目标地址必须是包含该goto指令的方法中的指令操作码的地址。
goto_w
运作
始终分支(宽索引)
格式
ǞǞǞ
branchbyte1
branchbyte2
branchbyte3
branchbyte4
表格
goto_w = 200 (0xc8)
操作数栈
无变化
描述
无符号字节 branchbyte1, branchbyte2, branchbyte3, 和 branchbyte4 被用来构建一个有符号的32位分支偏移,其中 branchoffset 是( branchbyte1 << 24 )|( branchbyte2 << 16 )|( branchbyte3 << 8 )| branchbyte4。从这条goto_w指令的操作码的地址开始,执行在这个偏移量上进行。目标地址必须是包含这条goto_w指令的方法中的一个指令的操作码的地址。
笔记
尽管goto_w指令需要一个4字节的分支偏移,但其他因素限制了一个方法的大小为65535字节(§4.11)。这个限制可能会在未来的Java虚拟机版本中被提高。
i2b
运作
将int转换为byte
格式
i2b
表格
i2b = 145 (0x91)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型。它被从操作数栈中弹出,截断为一个字节,然后用符号扩展为一个int结果。这个结果被推到操作数栈上。
笔记
i2b指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息。其结果也可能与值的符号不一样。
i2c
运作
将int转换为char
格式
i2c
表格
i2c = 146 (0x92)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型。它被从操作数堆栈中弹出,截断为char,然后零扩展为一个int结果。这个结果被推到操作数栈上。
笔记
i2c指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息。结果(总是正的)也可能与值的符号不一致。
i2d
运作
将int转换为double
格式
i2d
表格
i2d = 135 (0x87)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型的。它被从操作数堆栈中弹出并转换为一个双倍的结果。这个结果被推到操作数栈上。
笔记
i2d指令执行了一个扩大的原始转换(JLS§5.1.2)。因为所有int类型的值都可以用double类型精确表示,所以转换是精确的。
i2f
运作
将int转换为float
格式
i2f
表格
i2f = 134 (0x86)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型的。它被从操作数堆栈中弹出,并使用IEEE 754四舍五入模式转换为浮点数结果。该结果被推到操作数堆栈上。
笔记
i2f指令执行加宽的原始转换(JLS §5.1.2),但是可能会导致精度的损失,因为float类型的值只有24个有效位。
i2l
运作
将int转换为long
格式
i2l
表格
i2l = 133 (0x85)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型的。它被从操作数栈中弹出,并被符号扩展为一个长的结果。这个结果被推到操作数栈上。
笔记
i2l指令执行了一个加宽的原始转换(JLS§5.1.2)。因为所有int类型的值都可以用long类型精确表示,所以这个转换是精确的。
i2s
运作
将int转换为short
格式
i2s
表格
i2s = 147 (0x93)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是int类型。它被从操作数堆栈中弹出,截断为short,然后用符号扩展为int结果。这个结果被推到操作数栈上。
笔记
i2s指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息。其结果也可能与值的符号不一样。
iadd
运作
添加int
格式
添置
表格
iadd = 96 (0x60)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数堆栈中被弹出。int的结果是value1 + value2。这个结果被推到操作数堆栈中。
结果是真正的数学结果的32个低阶位,以足够宽的二补格式,表示为一个int类型的值。如果发生溢出,那么结果的符号可能与两个值的数学和的符号不一样。
尽管有可能发生溢出,但执行iadd指令时不会抛出运行时异常。
iaload
运作
从数组中加载int
格式
iaload
表格
iaload = 46 (0x2e)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型,并且必须指向一个组件为int类型的数组。索引必须是int类型的。arrayref和index都从操作数栈中被弹出。索引处的数组组件中的int值被检索并推到操作数栈中。
运行时异常情况
如果arrayref\为空,iaload\将抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,iaload\指令会抛出一个ArrayIndexOutOfBoundsException。
iand
运作
布尔型和int型
格式
i和
表格
iand = 126 (0x7e)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。它们被从操作数堆栈中弹出。通过对value1和value2进行位数和(连接)计算出一个int结果。该结果被推到操作数堆栈中。
iastore\
运作
存储到int数组中
格式
iastore
表格
iastore = 79 (0x4f)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组成部分是int类型的。索引和值都必须是int类型的。arrayref, index, 和value被从操作数栈中弹出。int值被存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,iastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,iastore\指令会抛出ArrayIndexOutOfBoundsException。
iconst_\
运作
推进int常数
格式
iconst_
表格
iconst_m1 = 2 (0x2)
iconst_0 = 3 (0x3)
iconst_1 = 4 (0x4)
iconst_2 = 5 (0x5)
iconst_3 = 6 (0x6)
iconst_4 = 7 (0x7)
iconst_5 = 8 (0x8)
操作数栈
…→
…, < i>
描述
将int常数< i>(-1、0、1、2、3、4或5)推到操作数栈上。
笔记
这一系列指令中的每一条都等同于bipush < i>对< i>的相应值,只是操作数< i>是隐含的。
idiv
运作
除以英寸
格式
idiv
表格
idiv = 108 (0x6c)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。int结果是Java编程语言表达式value1/value2的值。该结果被推到操作数堆栈中。
一个int除法是向0取整的;也就是说,n/d中的int值产生的商是一个int值q,其大小尽可能大,同时满足|d⋅q| ≤|n|。此外,当|n|≥|d|且n和d的符号相同时,q为正,但当|n|≥|d|且n和d的符号相反时,q为负。
有一种特殊情况不符合这个规则:如果除数是int类型的最大可能的负整数,而除数是-1,那么就会发生溢出,结果等于除数。尽管有溢出,但在这种情况下没有抛出异常。
运行时异常
如果在一个int除法中除数的值是0,idiv\会抛出一个ArithmeticException。
if_acmp
运作
如果引用比较成功,就会有分支
格式
if_acmp<cond
branchbyte1
branchbyte2
表格
if_acmpeq = 165 (0xa5)
if_acmpne = 166 (0xa6)
操作数栈
…, value1, value2 →
…
描述
value1和value2都必须是引用类型。它们都从操作数栈中弹出并进行比较。比较的结果如下。
· if_acmpeq成功,当且仅当value1 = value2时
· if_acmpne成功,当且仅当value1≠value2时
如果比较成功,无符号的branchbyte1和branchbyte2被用来构建一个有符号的16位偏移量,其中偏移量被计算为(branchbyte1 << 8)| branchbyte2。然后从这个if_acmp指令的操作码的地址开始执行该偏移。目标地址必须是包含这个if_acmp指令的方法中的指令操作码的地址。
否则,如果比较失败,就在这条if_acmp指令后面的指令地址继续执行。
if_icmp
运作
如果int比较成功,就会有分支
格式
if_icmp<cond
branchbyte1
branchbyte2
表格
if_icmpeq = 159 (0x9f)
if_icmpne = 160 (0xa0)
if_icmplt = 161 (0xa1)
if_icmpge = 162 (0xa2)
if_icmpgt = 163 (0xa3)
if_icmple = 164 (0xa4)
操作数栈
…, value1, value2 →
…
描述
value1和value2都必须是int类型。它们都从操作数栈中弹出并进行比较。所有的比较都是有符号的。比较的结果如下。
· if_icmpeq成功,当且仅当value1 = value2时
· if_icmpne成功,当且仅当value1≠value2时
· if_icmplt成功,当且仅当value1 < value2时。
· 当且仅当值1≤值2时,if_icmple成功。
· if_icmpgt成功,当且仅当value1>value2时。
· if_icmpge成功,当且仅当value1≥value2时。
如果比较成功,无符号的branchbyte1和branchbyte2被用来构建一个有符号的16位偏移量,其中偏移量被计算为(branchbyte1 << 8)| branchbyte2。然后从这个if_icmp指令的操作码的地址开始执行该偏移。目标地址必须是包含这个if_icmp指令的方法中的指令操作码的地址。
否则,在这条if_icmp指令之后的指令地址执行。
if<cond>
运作
如果int与零的比较成功,就会有分支
格式
如果
branchbyte1
branchbyte2
表格
ifeq = 153 (0x99)
ifne = 154 (0x9a)
iflt = 155 (0x9b)
ifge = 156 (0x9c)
ifgt = 157 (0x9d)
ifle = 158 (0x9e)
操作数栈
…,值→
…
描述
该值必须是int类型。它被从操作数堆栈中弹出并与零进行比较。所有的比较都是有符号的。比较的结果如下。
· 如果且仅当值=0时,ifeq成功。
· 当且仅当值≠0时,ifne才会成功。
· 如果且仅当值<0时,iflt成功。
· 当且仅当值≤0时,ifle才会成功。
· 如果且仅当值>0时,ifgt成功。
· 当且仅当值≥0时,ifge成功。
如果比较成功,无符号的branchbyte1和branchbyte2被用来构造一个有符号的16位偏移量,其中偏移量被计算为(branchbyte1 << 8)| branchbyte2。然后从这个if指令的操作码的地址开始执行该偏移量。目标地址必须是包含该if指令的方法中的指令操作码的地址。
否则,在这条if指令之后的指令地址执行。
ifnonnull
运作
如果引用不为空,则为分支
格式
如果没有
branchbyte1
branchbyte2
表格
ifnonnull = 199 (0xc7)
操作数栈
…,值→
…
描述
该值必须是引用类型。它被从操作数栈中弹出。如果值不是空的,无符号的branchbyte1和branchbyte2被用来构造一个有符号的16位偏移量,其中偏移量被计算为(branchbyte1 << 8)| branchbyte2。然后从这条ifnonnull指令的操作码的地址开始执行该偏移量。目标地址必须是包含该ifnonnull指令的方法中的指令操作码的地址。
否则,在这条ifnonnull指令之后的指令地址执行。
ifnull
运作
如果引用是空的,就会出现分支
格式
如果无效
branchbyte1
branchbyte2
表格
ifnull = 198 (0xc6)
操作数栈
…,值→
…
描述
该值必须是引用类型。它被从操作数栈中弹出。如果值为空,无符号的branchbyte1和branchbyte2被用来构建一个有符号的16位偏移量,其中偏移量被计算为(branchbyte1 << 8)| branchbyte2。然后从这个ifnull指令的操作码的地址开始执行该偏移。目标地址必须是包含该ifnull指令的方法中的指令操作码的地址。
否则,在这条ifnull指令之后的指令地址执行。
iinc\
运作
用常数增加本地变量
格式
iinc
指数
构成
表格
iinc = 132 (0x84)
操作数栈
无变化
描述
index是一个无符号的字节,必须是当前帧的局部变量阵列的索引(§2.6)。const是一个有符号的即时字节。索引处的局部变量必须包含一个int。const的值首先被符号扩展为一个int,然后索引处的局部变量被增加了这个量。
笔记
iinc操作码可以和wide指令(§wide)一起使用,使用一个两字节的无符号索引访问一个局部变量,并以一个两字节的即时有符号值来增加它。
iload
运作
从本地变量加载int
格式
负载
指数
表格
iload = 21 (0x15)
操作数栈
…→
…,价值
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。索引处的局部变量必须包含一个int。索引处的局部变量的值被推到操作数栈上。
笔记
iload操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
iload_
运作
从本地变量加载int
格式
负载_
表格
iload_0 = 26 (0x1a)
iload_1 = 27 (0x1b)
iload_2 = 28 (0x1c)
iload_3 = 29 (0x1d)
操作数栈
…→
…,价值
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。< n>处的局部变量必须包含一个int。< n>处的局部变量的值被推到操作数栈上。
笔记
每条iload_指令都与索引为的iload相同,只是操作数是隐含的。
imul
运作
乘法运算
格式
imul
表格
imul = 104 (0x68)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。int的结果是value1 value2。该结果被推到操作数堆栈中。
结果是真正的数学结果的32个低阶位,以足够宽的二补格式,表示为一个int类型的值。如果发生溢出,那么结果的符号可能与两个值的数学乘法的符号不一样。
尽管可能会发生溢出,但执行imul指令时绝不会抛出一个运行时异常。
ineg
运作
负数
格式
ineg
表格
ineg = 116 (0x74)
操作数栈
…,值→
…,结果
描述
该值必须是int类型。它被从操作数栈中弹出。int的结果是值的算术否定,-值。该结果被推到操作数堆栈中。
对于int值,否定与从零减去相同。因为Java虚拟机对整数使用二补表示法,而二补值的范围不是对称的,对最大负数int的否定结果也是最大负数。尽管发生了溢出,但没有抛出异常。
对于所有的int值x,-x等于(~x)+1。
insatnceof
运作
判断对象是否属于给定类型
格式
的实例
indexbyte1
indexbyte2
表格
instanceof = 193 (0xc1)
操作数栈
…, objectref →
…,结果
描述
objectref,必须是引用类型的,从操作数栈中弹出。无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。索引处的运行时常量池项目必须是对类、数组或接口类型的符号引用。
如果objectref为空,instanceof指令将一个0的int结果作为一个int推到操作数堆栈上。
否则,命名的类、数组或接口类型被解析(§5.4.3.1)。如果objectref是被解析的类或数组的实例,或者实现了被解析的接口,instanceof指令将一个int结果1作为一个int推到操作数栈上;否则,它将推到一个int结果0。
下面的规则被用来确定一个非空的objectref是否是被解析类型的一个实例。如果S是objectref所引用的对象的类,T是被解析的类、数组或接口类型,instanceof确定objectref是否是T的一个实例,如下所示。
· 如果S是一个普通的(非数组)类,那么。
o 如果T是一个类的类型,那么S必须和T是同一个类,或者S必须是T的一个子类。
o 如果T是一个接口类型,那么S必须实现接口T。
· 如果S是一个接口类型,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是与S相同的接口或S的超接口。
· 如果S是一个代表数组类型SC[]的类,也就是说,一个由SC类型的组件组成的数组,那么。
o 如果T是一个类的类型,那么T必须是Object。
o 如果T是一个接口类型,那么T必须是由数组实现的接口之一(JLS §4.10.3)。
o 如果T是一个TC[]类型的数组,也就是一个TC类型的组件数组,那么下列情况之一必须为真。
§ TC和SC是同一个原始类型。
§ TC和SC是引用类型,SC类型可以通过这些运行时规则被投射到TC。
链接异常情况
在解析对类、数组或接口类型的符号引用时,可以抛出§5.4.3.1中记载的任何异常。
笔记
instanceof指令与checkcast指令(§checkcast)非常相似。它的不同之处在于对null的处理,测试失败时的行为(checkcast抛出一个异常,instanceof推送一个结果代码),以及对操作数栈的影响。
invokedynamic
运作
调用动态方法
格式
调用的动态
indexbyte1
indexbyte2
0
0
表格
invokedynamic = 186 (0xba)
操作数栈
…, [arg1, [arg2 …]] 。→
…
描述
被调用的动态指令的每个具体的词法出现被称为动态调用站点。
首先,无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。在该索引处的运行时常量池项目必须是对一个调用站点指定符的符号引用(§5.1)。第三和第四个操作数字节的值必须始终为零。
为这个特定的动态调用站点解析调用站点指定符(§5.4.3.6),以获得对将作为引导方法的java.lang.invoke.MethodHandle实例的引用,对java.lang.invoke.MethodType实例的引用,以及对静态参数的引用。
接下来,作为继续解决调用站点指定符的一部分,引导方法被调用,就像执行一个invokevirtual指令(§invokevirtual)一样,该指令包含一个运行时常量池索引到一个符号引用R,其中。
· R是对一个类的方法的符号化引用(§5.1)。
· 为要找到方法的类的符号引用,R指定java.lang.invoke.MethodHandle。
· 为方法的名称,R指定调用。
· 为方法的描述符,R指定了java.lang.invoke.CallSite的返回类型,以及从操作数栈上推送的项目中得到的参数类型。
前三个参数类型是java.lang.invoke.MethodHandles.Lookup、String和java.lang.invoke.MethodType,按顺序排列。如果调用站点指定器有任何静态参数,那么每个参数的参数类型将按照参数被推入操作数栈的顺序附加到方法描述符的参数类型上。这些参数类型可以是Class、java.lang.invoke.MethodHandle、java.lang.invoke.MethodType、String、int、long、float或double。
而在这里,就像以下项目被依次推到了操作数堆栈上一样。
· 指对引导方法的java.lang.invoke.MethodHandle对象的引用。
· 一个对java.lang.invoke.MethodHandles.Lookup对象的引用,这个动态调用站点发生在该类中。
· 对调用站点指定器中方法名称的String的引用。
· 指在调用站点指定器中为方法描述符获得的对java.lang.invoke.MethodType对象的引用。
· 对类、方法类型、方法句柄和字符串字面的引用在调用站点指定符中表示为静态参数,以及数字值(§2.3.1, §2.3.2)在调用站点指定符中表示为静态参数,按照它们在调用站点指定符中出现的顺序。(也就是说,对原始值不进行框选)。
符号引用R描述了一个签名多态的方法(§2.9)。由于 invokevirtual 对名为 invoke 的签名多态方法的操作,接收方法句柄(代表引导方法)的类型描述符不需要在语义上等同于 R 所指定的方法描述符。例如,R 所指定的第一个参数类型可以是 Object 而不是 java.lang.invoke.MethodHandles.Lookup,R 所指定的返回类型可以是 Object 而不是 java.lang.invoke.CallSite。只要bootstrap方法能被invoke方法调用而不抛出java.lang.invoke.WrongMethodTypeException,代表bootstrap方法的方法句柄的类型描述符就是任意的。
如果bootstrap方法是一个可变节数的方法,那么上面指定的操作数栈上的部分或全部参数可以被收集到一个尾数组参数中。
引导方法的调用发生在一个线程中,该线程正在尝试解决对该动态调用站点的调用站点指定符的符号引用。如果有几个这样的线程,引导方法可能在几个线程中同时被调用。因此,访问全局应用程序数据的引导方法必须采取通常的预防措施,以防止竞赛条件。
Bootstrap方法返回的结果必须是对一个对象的引用,该对象的类是java.lang.invoke.CallSite或java.lang.invoke.CallSite的一个子类。这个对象被称为调用站点对象。该引用被从操作数堆栈中弹出,就像在执行 invokevirtual 指令时一样。
如果几个线程同时为同一个动态调用站点执行引导方法,Java虚拟机必须选择一个返回的调用站点对象并将其明显地安装给所有线程。任何其他为动态调用站点执行的引导方法都被允许完成,但它们的结果会被忽略,线程对动态调用站点的执行会以选择的调用站点对象进行。
调用站点对象有一个类型描述符(java.lang.invoke.MethodType的实例),它必须在语义上等同于为调用站点指定器中的方法描述符获得的java.lang.invoke.MethodType对象。
成功的调用站点指定器解析的结果是一个调用站点对象,它被永久地绑定到动态调用站点。
由绑定的调用站点对象的目标所代表的方法句柄被调用。调用的发生就像执行一个invokevirtual指令(§invokevirtual),该指令表示一个运行时常量池索引到一个具有以下属性的方法(§5.1)的符号引用。
· 该方法的名称是invokeExact。
· 该方法的描述符是调用站点指定器中的方法描述符;以及
· 该方法在其中的符号引用表示java.lang.invoke.MethodHandle类。
操作数栈将被解释为包含对调用站点对象的引用,然后是nargs参数值,其中数值的数量、类型和顺序必须与调用站点指定器中的方法描述符一致。
链接异常情况
如果对调用站点指定器的符号引用的解析抛出了一个异常E,那么\被调用的动态\指令会抛出一个包裹着E的BootstrapMethodError。
否则,在继续解决调用站点指定符的过程中,如果因为抛出异常E,bootstrap方法的调用突然完成(§2.6.5),invokedynamic\指令会抛出一个包裹E的BootstrapMethodError(如果bootstrap方法有错误的arity、参数类型或返回类型,导致java.lang.invoke.MethodHandle . invoke抛出java.lang.invoke.WrightMethodTypeException,这可能发生)。
否则,在继续解决调用站点指定符的过程中,如果来自bootstrap方法调用的结果不是对java.lang.invoke.CallSite实例的引用,invokedynamic\指令会抛出一个BootstrapMethodError。
否则,在继续解析调用站点指定符的过程中,如果调用站点对象的目标类型描述符在语义上不等于调用站点指定符中的方法描述符,则\调用的动态\指令会抛出BootstrapMethodError。
运行时异常情况
如果这个特定的动态调用站点完成了对其调用站点指定符的解析,就意味着对java.lang.invoke.CallSite实例的非空引用被绑定到这个动态调用站点。因此,代表对调用站点对象目标的引用的操作数栈项永远不会为空。同样,它意味着调用站点指定器中的方法描述符在语义上等同于要被调用的方法句柄的类型描述符,就像执行 invokevirtual 指令一样。这些不变性意味着与调用站点对象绑定的invokedynamic指令不会抛出NullPointerException或java.lang.invoke.WrongMethodTypeException。
invokeinterface
运作
调用接口方法
格式
调用界面
indexbyte1
indexbyte2
计数
0
表格
invokeinterface = 185 (0xb9)
操作数栈
…, objectref, [arg1, [arg2 …]→
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对接口方法的符号引用(§5.1),它给出了接口方法的名称和描述符(§4.3.3),以及对接口方法所在接口的符号引用。命名的接口方法被解析(§5.4.3.4)。
被解决的接口方法不能是一个实例初始化方法,也不能是类或接口的初始化方法(§2.9)。
count操作数是一个无符号字节,不能为零。objectref必须是引用类型,并且在操作数堆栈中必须有nargs参数值,其中数值的数量、类型和顺序必须与解析的接口方法的描述符一致。第四个操作数字节的值必须始终为零。
让C是objectref的类。实际要调用的方法是由以下查找过程选择的。
\1. 如果C包含一个实例方法的声明,其名称和描述符与解析的方法相同,那么它就是要被调用的方法。
\2. 否则,如果C有一个超类,就会搜索一个与被解析的方法具有相同名称和描述符的实例方法的声明,从C的直接超类开始,继续搜索该类的直接超类,以此类推,直到找到匹配的方法或者不存在其他超类。如果找到了一个匹配的方法,那么它就是要被调用的方法。
\3. 否则,如果在 C 的超接口中正好有一个最大化的特定方法(§5.4.3.3)与被解决的方法的名称和描述符相匹配,并且不是抽象的,那么它就是要被调用的方法。
如果该方法是同步的,与objectref相关的监视器被进入或重新进入,就像在当前线程中执行monitorenter指令(§monitorenter)。
如果该方法不是本地的,nargs参数值和objectref将从操作数栈中弹出。在Java虚拟机栈中为被调用的方法创建一个新的框架。objectref和参数值连续成为新框架的局部变量的值,objectref在局部变量0中,arg1在局部变量1中(如果arg1是long或double类型,则在局部变量1和2中),以此类推。任何浮点类型的参数值在存储到局部变量之前都要经过数值集转换(§2.8.3)。然后,新的框架被变成当前的,Java虚拟机pc被设置为要调用的方法的第一条指令的操作码。继续执行该方法的第一条指令。
如果该方法是本地的,并且实现该方法的平台相关代码还没有被绑定(§5.6)到Java虚拟机中,那就完成了。nargs参数值和objectref从操作数栈中弹出,作为参数传递给实现该方法的代码。任何浮点类型的参数值在作为参数传递之前都要经过值集转换(§2.8.3)。参数的传递和代码的调用都是依赖于实现方式的。当依赖平台的代码返回时。
· 如果本地方法是同步的,与objectref相关的监视器就会被更新,并且可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。
· 如果本机方法返回一个值,依赖平台的代码的返回值会以一种依赖实现的方式转换为本机方法的返回类型,并推到操作数堆栈上。
链接异常情况
在解析对接口方法的符号引用时,任何与接口方法解析有关的异常(§5.4.3.4)都可以被抛出。
否则,如果解决的方法是静态或私有的,invokeinterface\指令会抛出一个IncompatibleClassChangeError。
运行时异常情况
否则,如果objectref\为空,invokeinterface\指令会抛出一个NullPointerException。
否则,如果objectref\的类没有实现解决的接口,invokeinterface\会抛出一个IncompatibleClassChangeError。
否则,如果查找过程的第1步或第2步选择了一个非公开的方法,invokeinterface\会抛出一个IllegalAccessError。
否则,如果查找过程的第1步或第2步选择了一个抽象方法,invokeinterface\会抛出一个AbstractMethodError。
否则,如果查找过程的第1步或第2步选择了一个本地方法,而实现该方法的代码不能被绑定,invokeinterface\会\抛出一个UnsatisfiedLinkError。
否则,如果查找过程的第3步确定在C的超接口中存在多个与被解决的方法的名称和描述符相匹配并且不是抽象的特定方法,invokeinterface\会抛出一个IncompatibleClassChangeError。
否则,如果查找过程的第3步确定在C的超接口中,有零个与被解决的方法的名称和描述符相匹配的最大特定方法,并且不是抽象的,invokeinterface\会抛出一个AbstractMethodError。
笔记
invokeinterface指令的count操作数记录了对参数值数量的衡量,其中long或double类型的参数值对count值有两个单位的贡献,任何其他类型的参数有一个单位的贡献。这一信息也可以从所选方法的描述符中得到。冗余是历史性的。
第四个操作数字节的存在是为了给Oracle的某些Java虚拟机实现中使用的额外操作数保留空间,这些实现在运行时用一个专门的伪指令代替invokeinterface指令。为了向后兼容,它必须被保留。
nargs参数值和objectref与第一个nargs+1局部变量不是一一对应的。long和double类型的参数值必须存储在两个连续的局部变量中,因此可能需要多个nargs局部变量来将nargs参数值传递给被调用的方法。
选择逻辑允许在超接口中声明的非抽象方法被选中。接口中的方法只有在类的层次结构中没有匹配的方法时才会被考虑。如果在超界面层次中存在两个非抽象方法,而这两个方法都不比另一个更具体,那么就会发生错误;我们不会试图消除歧义(例如,一个可能是被引用的方法,一个可能是不相关的,但是我们不喜欢被引用的方法)。另一方面,如果有很多抽象方法,但只有一个非抽象方法,那么就选择非抽象方法(除非一个抽象方法更具体)。
invokespecial
运作
调用实例方法;对超类、私有和实例初始化方法的调用进行特殊处理
格式
调用特殊
indexbyte1
indexbyte2
表格
invokespecial = 183 (0xb7)
操作数栈
…, objectref, [arg1, [arg2 …]→
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对方法或接口方法的符号引用(§5.1),它给出了方法的名称和描述符(§4.3.3),以及对要找到该方法的类或接口的符号引用。被命名的方法被解析(§5.4.3.3,§5.4.3.4)。
如果被解决的方法是被保护的,并且它是当前类的超类的成员,并且该方法不是在与当前类相同的运行时包(§5.3)中声明的,那么objectref的类必须是当前类或者是当前类的子类。
如果以下所有情况都是真的,让C是当前类的直接超类。
· 解决的方法不是一个实例初始化方法(§2.9)。
· 如果符号引用命名了一个类(不是一个接口),那么这个类就是当前类的超类。
· ACC_SUPER标志是为类文件设置的(§4.1)。
否则,让C是由符号引用命名的类或接口。
要调用的实际方法是由以下查找程序选择的。
\1. 如果C包含一个实例方法的声明,其名称和描述符与解析的方法相同,那么它就是要被调用的方法。
\2. 否则,如果 C 是一个类,并且有一个超类,那么就会搜索一个与所解析的方法具有相同名称和描述符的实例方法的声明,从 C 的直接超类开始,继续搜索该类的直接超类,以此类推,直到找到一个匹配的方法或者不存在其他的超类。如果找到了一个匹配的方法,那么它就是要被调用的方法。
\3. 否则,如果C是一个接口,并且Object类包含一个公共实例方法的声明,其名称和描述符与解析的方法相同,那么它就是要被调用的方法。
\4. 否则,如果在 C 的超接口中正好有一个最大化的特定方法(§5.4.3.3)与被解决的方法的名称和描述符相匹配,并且不是抽象的,那么它就是要被调用的方法。
objectref必须是引用类型,并且必须在操作栈上紧跟nargs参数值,其中参数值的数量、类型和顺序必须与所选实例方法的描述符一致。
如果该方法是同步的,与objectref相关的监视器被进入或重新进入,就像在当前线程中执行monitorenter指令(§monitorenter)。
如果该方法不是本地的,nargs参数值和objectref将从操作数栈中弹出。在Java虚拟机栈中为被调用的方法创建一个新的框架。objectref和参数值连续成为新框架的局部变量的值,objectref在局部变量0中,arg1在局部变量1中(如果arg1是long或double类型,则在局部变量1和2中),以此类推。任何浮点类型的参数值在存储到局部变量之前都要经过数值集转换(§2.8.3)。然后,新的框架被变成当前状态,Java虚拟机pc被设置为要调用的方法的第一条指令的操作码。继续执行该方法的第一条指令。
如果该方法是本地的,并且实现该方法的平台相关代码还没有被绑定(§5.6)到Java虚拟机中,那就完成了。nargs参数值和objectref从操作数栈中弹出,作为参数传递给实现该方法的代码。任何浮点类型的参数值在作为参数传递之前都要经过值集转换(§2.8.3)。参数的传递和代码的调用都是依赖于实现方式的。当依赖平台的代码返回时,会发生以下情况。
· 如果本地方法是同步的,与objectref相关的监视器就会被更新,并且可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。
· 如果本机方法返回一个值,依赖平台的代码的返回值会以一种依赖实现的方式转换为本机方法的返回类型,并推到操作数堆栈上。
链接异常情况
在对方法的符号引用的解析过程中,任何与方法解析相关的异常(§5.4.3.3)都可以被抛出。
否则,如果解决的方法是一个实例初始化方法,而它所声明的类不是指令所象征性引用的类,则会抛出NoSuchMethodError。
否则,如果解决的方法是一个类(静态)方法, invokespecial\指令会抛出一个IncompatibleClassChangeError。
运行时异常情况
否则,如果objectref\为空,invokespecial\指令会抛出一个NullPointerException。
否则,如果解决的方法是当前类的超类的保护方法,在不同的运行时包中声明,并且objectref\的类不是当前类或当前类的子类,那么invokespecial\会抛出\一个IllegalAccessError。
否则,如果查找过程的第1步、第2步或第3步选择了一个抽象方法,invokespecial\会抛出一个AbstractMethodError。
否则,如果查找过程的第1步、第2步或第3步选择了一个本地方法,而实现该方法的代码不能被绑定,invokespecial\会\抛出一个UnsatisfiedLinkError。
否则,如果查找过程的第4步确定在C的超接口中存在多个与被解决的方法的名称和描述符相匹配并且不是抽象的特定方法,invokespecial\就会抛出一个不兼容的ClassChangeError。
否则,如果查找过程的第4步确定在C的超接口中,有零个与被解决的方法的名称和描述符相匹配的最大特定方法,并且不是抽象的,invokespecial\会\抛出一个AbstractMethodError。
笔记
invokespecial指令和invokevirtual指令(§invokevirtual)的区别在于,invokevirtual是根据对象的类来调用方法。invokespecial指令用于调用实例初始化方法(§2.9),以及当前类的私有方法和超类的方法。
invokespecial指令在JDK 1.0.2版之前被命名为invokenonvirtual。
nargs参数值和objectref与第一个nargs+1局部变量不是一一对应的。long和double类型的参数值必须存储在两个连续的局部变量中,因此可能需要多个nargs局部变量来将nargs参数值传递给被调用的方法。
invokespecial指令处理私有接口方法的调用,通过直接超接口引用的非抽象接口方法,以及通过超类引用的非抽象接口方法。在这些情况下,选择的规则与invokeinterface的规则基本相同(除了搜索从不同的类开始)。
invokestatic
运作
调用一个类(静态)方法
格式
invokestatic
indexbyte1
indexbyte2
表格
invokestatic = 184 (0xb8)
操作数栈
…, [arg1, [arg2 …]] 。→
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对方法或接口方法的符号引用(§5.1),它给出了方法的名称和描述符(§4.3.3),以及对要找到该方法的类或接口的符号引用。被命名的方法被解析(§5.4.3.3)。
被解决的方法不能是一个实例初始化方法,也不能是类或接口的初始化方法(§2.9)。
解决的方法必须是静态的,因此不能是抽象的。
在成功解析该方法时,如果声明该解析方法的类或接口尚未被初始化,则该类或接口被初始化(§5.5)。
操作数栈必须包含nargs参数值,其中参数值的数量、类型和顺序必须与解析方法的描述符一致。
如果该方法是同步的,与被解析的Class对象相关的监视器会被进入或重新进入,就像在当前线程中执行monitorenter指令一样(§monitorenter)。
如果该方法不是本地的,nargs参数值将从操作数栈中弹出。在Java虚拟机栈中为被调用的方法创建一个新的框架。nargs参数值连续成为新框架的局部变量的值,arg1在局部变量0中(如果arg1是long或double类型,则在局部变量0和1中),以此类推。任何浮点类型的参数值在存储到局部变量之前都要经过数值集转换(§2.8.3)。然后,新的框架被变成当前状态,Java虚拟机pc被设置为要调用的方法的第一条指令的操作码。继续执行该方法的第一条指令。
如果该方法是本地的,并且实现该方法的依赖平台的代码还没有被绑定(§5.6)到Java虚拟机中,这就完成了。nargs参数值从操作数栈中弹出,作为参数传递给实现该方法的代码。任何浮点类型的参数值在作为参数传递之前都要经过值集转换(§2.8.3)。参数的传递和代码的调用都是依赖于实现方式的。当依赖平台的代码返回时,会发生以下情况。
· 如果本地方法是同步的,那么与被解析的Class对象相关的监视器就会被更新,并且可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。
· 如果本机方法返回一个值,依赖平台的代码的返回值会以一种依赖实现的方式转换为本机方法的返回类型,并推到操作数堆栈上。
链接异常情况
在对方法的符号引用的解析过程中,任何与方法解析相关的异常(§5.4.3.3)都可以被抛出。
否则,如果解决的方法是一个实例方法, invokestatic\指令会抛出一个IncompatibleClassChangeError。
运行时异常情况
否则,如果这个invokestatic\指令的执行导致被引用的类或接口的初始化,invokestatic\可能会抛出一个错误,详见§5.5。
否则,如果解决的方法是本地的,并且实现该方法的代码不能被绑定,invokestatic\会\抛出一个UnsatisfiedLinkError。
笔记
nargs参数值与第一个nargs局部变量不是一一对应的。long和double类型的参数值必须存储在两个连续的局部变量中,因此可能需要多个nargs局部变量来将nargs参数值传递给被调用的方法。
invokevirtual
运作
调用实例方法;基于类的调度
格式
调用虚拟
indexbyte1
indexbyte2
表格
invokevirtual = 182 (0xb6)
操作数栈
…, objectref, [arg1, [arg2 …]→
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个方法的符号引用(§5.1),它给出了该方法的名称和描述符(§4.3.3),以及对该方法所在类的符号引用。被命名的方法被解析(§5.4.3.3)。
被解决的方法不能是一个实例初始化方法,也不能是类或接口的初始化方法(§2.9)。
如果被解决的方法是被保护的,并且它是当前类的超类的成员,并且该方法不是在与当前类相同的运行时包(§5.3)中声明的,那么objectref的类必须是当前类或者是当前类的子类。
如果被解析的方法不是签名多态的(§2.9),那么 invokevirtual 指令的流程如下。
让C是objectref的类。实际要调用的方法是由以下查找过程选择的。
\1. 如果C包含一个实例方法m的声明,它覆盖了(§5.4.5)已解决的方法,那么m就是要被调用的方法。
\2. 否则,如果C有一个超类,那么就从C的直接超类开始,继续从该类的直接超类开始,依此类推,寻找覆盖该方法的实例方法的声明,直到找到覆盖方法或者不存在其他超类。如果找到了一个覆盖方法,它就是要被调用的方法。
\3. 否则,如果在 C 的超接口中正好有一个最大化的特定方法(§5.4.3.3)与被解决的方法的名称和描述符相匹配,并且不是抽象的,那么它就是要被调用的方法。
在操作数堆栈中,objectref后面必须有nargs参数值,这些参数值的数量、类型和顺序必须与所选实例方法的描述符一致。
如果该方法是同步的,与objectref相关的监视器被进入或重新进入,就像在当前线程中执行monitorenter指令(§monitorenter)。
如果该方法不是本地的,nargs参数值和objectref将从操作数栈中弹出。在Java虚拟机栈中为被调用的方法创建一个新的框架。objectref和参数值连续成为新框架的局部变量的值,objectref在局部变量0中,arg1在局部变量1中(如果arg1是long或double类型,则在局部变量1和2中),以此类推。任何浮点类型的参数值在存储到局部变量之前都要经过数值集转换(§2.8.3)。然后,新的框架被变成当前的,Java虚拟机pc被设置为要调用的方法的第一条指令的操作码。继续执行该方法的第一条指令。
如果该方法是本地的,并且实现该方法的平台相关代码还没有被绑定(§5.6)到Java虚拟机中,那就完成了。nargs参数值和objectref从操作数栈中弹出,作为参数传递给实现该方法的代码。任何浮点类型的参数值在作为参数传递之前都要经过值集转换(§2.8.3)。参数的传递和代码的调用都是依赖于实现方式的。当依赖平台的代码返回时,会发生以下情况。
· 如果本地方法是同步的,与objectref相关的监视器就会被更新,并且可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。
· 如果本机方法返回一个值,依赖平台的代码的返回值会以一种依赖实现的方式转换为本机方法的返回类型,并推到操作数堆栈上。
如果被解析的方法是签名多态的(§2.9),那么 invokevirtual 指令的流程如下。
首先,获得对java.lang.invoke.MethodType实例的引用,就像通过解析对方法类型(§5.4.3.5)的符号引用一样,其参数和返回类型与invokevirtual指令所引用的方法描述符相同。
· 如果命名的方法是invokeExact,java.lang.invoke.MethodType的实例必须在语义上等于接收方法句柄objectref的类型描述符。被调用的方法句柄是objectref。
· 如果命名的方法是invoke,并且java.lang.invoke.MethodType的实例在语义上等同于接收方法句柄objectref的类型描述符,那么被调用的方法句柄就是objectref。
· 如果命名的方法是invoke,并且java.lang.invoke.MethodType的实例在语义上不等于接收方法句柄objectref的类型描述符,那么Java虚拟机试图调整接收方法句柄的类型描述符,就像调用java.lang.invoke.MethodHandle.asType一样,以获得一个完全可被调用的方法句柄m。
在操作数堆栈中,objectref后面必须有nargs参数值,这些参数值的数量、类型和顺序必须与要调用的方法句柄的类型描述符一致。(这个类型描述符将对应于适合被调用的方法句柄类型的方法描述符,如§5.4.3.5所规定)。
然后,如果要调用的方法句柄有字节码行为,Java虚拟机就会通过执行与方法句柄的种类相关的字节码行为来调用该方法句柄。如果种类是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)、8(REF_newInvokeSpecial)或9(REF_invokeInterface),那么在执行字节码行为的过程中,将创建一个框架并使之成为当前状态。当被字节码行为调用的方法完成时(正常或突然),其调用者的框架被认为是包含该 invokevirtual 指令的方法的框架。
字节码行为本身执行的框架是不可见的。
否则,如果要调用的方法句柄没有字节码行为,Java虚拟机将以一种与实现相关的方式调用它。
链接异常情况
在对方法的符号引用的解析过程中,任何与方法解析相关的异常(§5.4.3.3)都可以被抛出。
否则,如果解决的方法是一个类(静态)方法,invokevirtual\指令会抛出一个IncompatibleClassChangeError。
否则,如果被解析的方法是签名多态的,那么在解析从该方法的符号引用中的描述符派生的方法类型时,可以抛出与方法类型解析有关的任何异常(§5.4.3.5)。
运行时异常情况
否则,如果objectref\为空,invokevirtual\指令会抛出一个NullPointerException。
否则,如果解决的方法是当前类的超类的保护方法,在不同的运行时包中声明,并且objectref\的类不是当前类或当前类的子类,那么invokevirtual\会抛出一个IllegalAccessError。
否则,如果解决的方法不是签名多态的。
· 如果查找过程的第1步或第2步选择了一个抽象方法,invokevirtual\会抛出一个AbstractMethodError。
· 否则,如果查找过程的第1步或第2步选择了一个本地方法,而实现该方法的代码不能被绑定,invokevirtual\会抛出一个UnsatisfiedLinkError。
· 否则,如果查找过程的第3步确定在C的超接口中存在多个与被解决的方法的名称和描述符相匹配并且不是抽象的特定方法,invokevirtual\会抛出一个IncompatibleClassChangeError。
· 否则,如果查找过程的第3步确定在C的超接口中,有零个与被解决的方法的名称和描述符相匹配的最大特定方法,并且不是抽象的,invokevirtual\会抛出一个AbstractMethodError。
否则,如果解决的方法是签名多态的,那么。
· 如果方法名是invokeExact,并且获得的java.lang.invoke.MethodType实例在语义上不等于接收方法句柄的类型描述符,invokevirtual\指令会抛出一个java.lang.invoke.WrightMethodTypeException。
· 如果方法名是invoke,并且获得的java.lang.invoke.MethodType实例不是在接收方法句柄上调用的java.lang.invoke.MethodHandle.asType方法的有效参数,invokevirtual\指令会抛出一个java.lang.invoke.WrongMethodTypeException。
笔记
nargs参数值和objectref与第一个nargs+1局部变量不是一一对应的。long和double类型的参数值必须存储在两个连续的局部变量中,因此可能需要多个nargs局部变量来将nargs参数值传递给被调用的方法。
invokevirtual指令的符号引用有可能会解析到一个接口方法。在这种情况下,有可能在类的层次结构中没有重写方法,但是有一个非抽象的接口方法与解析的方法描述符相匹配。选择逻辑匹配这样的方法,使用与invokeinterface相同的规则。
ior
运作
布尔型或int型
格式
倡议者
表格
ior = 128 (0x80)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。它们被从操作数堆栈中弹出。通过对value1和value2进行位数全包OR计算出一个int结果。这个结果被推到操作数栈上。
irem
运作
剩余部分 int
格式
irem
表格
irem = 112 (0x70)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。int的结果是value1 - (value1 / value2) value2。该结果被推到操作数堆栈中。
irem指令的结果是(a/b)b+(a%b)等于a.即使在特殊情况下,即除数是其类型可能最大的负int,除数是-1(余数是0),这一特性也是成立的。从这一规则可以看出,只有当除数为负数时,余数运算的结果才可能是负数,只有当除数为正数时才可能是正数。此外,结果的大小总是小于除数的大小。
运行时异常
如果一个int余数运算符的除数值是0,irem\会抛出一个ArithmeticException。
归来\
运作
从方法中返回int
格式
ireturn
表格
ireturn = 172 (0xac)
操作数栈
…,值→
[空]
描述
当前方法的返回类型必须是boolean、byte、short、char或int。其值必须是int类型。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并可能被退出,就像在当前线程中执行monitorexit指令一样(§monitorexit)。如果没有抛出异常,值会从当前帧的操作数堆栈中弹出(§2.6)并推到调用者的帧的操作数堆栈中。当前方法的操作数栈中的任何其他值都被丢弃了。
然后解释器将控制权返回给方法的调用者,恢复调用者的框架。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,ireturn\会\抛出一个IllegalMonitorStateException。这种情况可能会发生,例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法的过程中违反了这些规则中的第一条,那么ireturn\会抛出一个IllegalMonitorStateException。
ishl
运作
向左移位 int
格式
ishl
表格
ishl = 120 (0x78)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。通过将value1左移s位来计算一个int结果,其中s是value2的低5位的值。这个结果被推到操作数栈上。
笔记
这相当于(即使发生溢出)乘以2的幂数s。实际使用的移位距离总是在0到31的范围内,包括在内,就像值2与掩码值0x1f进行位逻辑和。
ishr
运作
算术右移 int
格式
ishr
表格
ishr = 122 (0x7a)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。通过将value1右移s位,加上符号扩展,计算出一个int结果,其中s是value2的低5位的值。这个结果被推到操作数栈上。
笔记
得到的值是floor(value1 / 2s ),其中s是value2 & 0x1f。对于非负数的value1,这相当于截断int除以2的幂数s。实际使用的移位距离总是在0到31的范围内,包括在内,就像value2与掩码值0x1f进行了位逻辑和。
istore
运作
将int存储到本地变量中
格式
储存器
索引
表格
istore = 54 (0x36)
操作数栈
…,值→
…
描述
索引是一个无符号字节,必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是int类型的。它被从操作数栈中弹出,索引处的局部变量的值被设置为值。
笔记
istore操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
istore_
运作
将int存储到本地变量中
格式
istore_
表格
istore_0 = 59 (0x3b)
istore_1 = 60 (0x3c)
istore_2 = 61 (0x3d)
istore_3 = 62 (0x3e)
操作数栈
…,值→
…
描述
< n>必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是int类型的。它被从操作数栈中弹出,< n>处的局部变量的值被设置为值。
笔记
每条istore_指令都与索引为的istore相同,只是操作数是隐含的。
isub
运作
减去int
格式
isub
表格
isub = 100 (0x64)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数堆栈中被弹出。int的结果是value1 - value2。这个结果被推到操作数堆栈中。
对于int减法,a-b产生的结果与a+(-b)相同。对于int值,从0减去的结果与否定相同。
结果是真正的数学结果的32个低阶位,以足够宽的二进制补码格式,表示为一个int类型的值。如果发生溢出,那么结果的符号可能与两个值的数学差值的符号不一样。
尽管有可能发生溢出,但执行isub指令绝不会抛出一个运行时异常。
iushr
运作
逻辑性右移 int
格式
iushr
表格
iushr = 124 (0x7c)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。这些值从操作数栈中弹出。通过将value1右移s位计算出一个int结果,扩展为零,其中s是value2的低5位的值。这个结果被推到操作数栈上。
笔记
如果value1是正数,s是value2 & 0x1f,结果与value1 >> s相同;如果value1是负数,结果等于表达式(value1 >> s)+(2 << ~s)的值。(2 << ~s)项的增加抵消了传播的符号位。实际使用的移位距离总是在0到31的范围内,包括在内。
ixor
运作
Boolean XOR int
格式
ixor
表格
ixor = 130 (0x82)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是int类型。它们被从操作数堆栈中弹出。通过对value1和value2的位数排他性OR计算出一个int结果。这个结果被推到操作数栈上。
jsr
运作
跳转子程序
格式
jsr
branchbyte1
branchbyte2
表格
jsr = 168 (0xa8)
操作数栈
…→
…, 地址
描述
紧接着这条jsr指令的操作码的地址被推到操作数堆栈中,作为returnAddress类型的值。无符号的branchbyte1和branchbyte2被用来构建一个有符号的16位偏移量,其中偏移量为(branchbyte1 << 8)| branchbyte2。从这条jsr指令的地址开始,以该偏移量进行执行。目标地址必须是包含这条jsr指令的方法中的一条指令的操作码。
笔记
注意,jsr把地址推到操作数堆栈上,而ret(§ret)从局部变量中获取地址。这种不对称性是故意的。
在Java SE 6之前,Oracle为Java编程语言实现的编译器中,jsr指令与ret指令一起被用于实现finally子句(§3.13,§4.10.2.5)。
jsr_w
运作
跳转子程序(宽索引)
格式
jsr_w
branchbyte1
branchbyte2
branchbyte3
branchbyte4
表格
jsr_w = 201 (0xc9)
操作数栈
…→
…, 地址
描述
紧接着这条jsr_w指令的操作码的地址被推到操作数堆栈中,作为returnAddress类型的值。无符号的branchbyte1、branchbyte2、branchbyte3和branchbyte4被用来构建一个有符号的32位偏移量,其中偏移量为(branchbyte1<<24)|(branchbyte2<<16)|(branchbyte3<<8)|branchbyte4。从这个jsr_w指令的地址开始,按这个偏移量执行。目标地址必须是包含这条jsr_w指令的方法中的一条指令的操作码。
笔记
注意,jsr_w把地址推到操作数堆栈上,而ret(§ret)从局部变量中获取地址。这种不对称性是故意的。
在Java SE 6之前,Oracle为Java编程语言实现的编译器中,jsr_w指令与ret指令一起用于实现finally子句(§3.13,§4.10.2.5)。
尽管jsr_w指令需要一个4字节的分支偏移,但其他因素将方法的大小限制在65535字节(§4.11)。这个限制可能会在未来的Java虚拟机版本中被提高。
l2d
运作
将长数转换为双数
格式
l2d
表格
l2d = 138 (0x8a)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是long类型的。它被从操作数堆栈中弹出,并使用IEEE 754四舍五入模式转换为一个双倍结果。这个结果被推到操作数栈上。
笔记
l2d指令执行了一个加宽的原始转换(JLS §5.1.2),可能会损失精度,因为double类型的值只有53个有效位。
l2f
运作
将长数转换为浮点数
格式
l2f
表格
l2f = 137 (0x89)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是long类型的。它被从操作数堆栈中弹出,并使用IEEE 754四舍五入模式转换为浮点数结果。这个结果被推到操作数栈上。
笔记
l2f指令执行了一个加宽的原始转换(JLS §5.1.2),可能会损失精度,因为float类型的值只有24个显著位。
l2i
运作
将长数转换为英数
格式
l2i
表格
l2i = 136 (0x88)
操作数栈
…,值→
…,结果
描述
操作数堆栈顶部的值必须是long类型的。它被从操作数堆栈中弹出,并通过取长值的低阶32位和丢弃高阶32位转换为一个int结果。这个结果被推到操作数栈上。
笔记
l2i指令执行了一个缩小的原始转换(JLS §5.1.3)。它可能会丢失关于值的整体大小的信息。其结果也可能与值的符号不一样。
iadd
运作
添加长
格式
梯子
表格
ladd = 97 (0x61)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。这些值从操作数堆栈中被弹出。long的结果是value1 + value2。该结果被推到操作数堆栈中。
结果是真正的数学结果的64个低阶位,以足够宽的二补格式,表示为一个long类型的值。如果发生溢出,结果的符号可能与两个值的数学和的符号不一样。
尽管可能会发生溢出,但执行ladd指令永远不会抛出一个运行时异常。
laload
运作
从数组中加载长字符串
格式
laload
表格
laload = 47 (0x2f)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型的,并且必须指向一个数组,其组成部分是long类型的。索引必须是int类型的。arrayref和index都从操作数栈中被弹出。索引处的数组分量中的长值被检索出来并推到操作数栈中。
运行时异常情况
如果arrayref\为空,laoad\将\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,laoad\指令会抛出ArrayIndexOutOfBoundsException。
land
运作
布尔型和长型
格式
土地
表格
土地 = 127 (0x7f)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。它们被从操作数堆栈中弹出。通过取值1和值2的位和来计算一个长的结果。这个结果被推到操作数栈上。
lastore
运作
存储到长数组中
格式
lastore
表格
lastore = 80 (0x50)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型的,并且必须引用一个数组,其组成部分是long类型的。索引必须是int类型的,值必须是long类型的。arrayref, 索引, 和值从操作数栈中弹出。长值被存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,lastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,lastore\指令会抛出ArrayIndexOutOfBoundsException。
lcmp
运作
比较长
格式
lcmp
表格
lcmp = 148 (0x94)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。它们都被从操作数堆栈中弹出,并进行有符号的整数比较。如果value1大于value2,int值1被推到操作数栈中。如果value1等于value2,int值0被推到操作栈中。如果value1小于value2,int值-1被推到操作栈上。
lconst_
运作
推动长常数
格式
lconst_<l
表格
lconst_0 = 9 (0x9)
lconst_1 = 10 (0xa)
操作数栈
…→
…, < l>
描述
将长常数< l>(0或1)推到操作数栈上。
ldc
运作
从运行时常量库中推送项目
格式
ldc
索引
表格
ldc = 18 (0x12)
操作数栈
…→
…,价值
描述
索引是一个无符号字节,必须是当前类的运行时常量池的有效索引(§2.6)。索引处的运行时常量池条目必须是一个int或float类型的运行时常量,或者是对一个字符串字面的引用,或者是对一个类、方法类型或方法句柄的符号引用(§5.1)。
如果运行时常量池条目是int或float类型的运行时常量,则该运行时常量的数值将分别作为int或float推入操作数栈。
否则,如果运行时常量池条目是对代表字符串字面的String类实例的引用(§5.1),那么对该实例的引用value将被推入操作数栈。
否则,如果运行时常量池条目是对一个类的符号引用(§5.1),那么命名的类将被解析(§5.4.3.1),并将对代表该类的类对象的引用值推到操作数栈上。
否则,运行时常量池条目必须是对方法类型或方法柄的符号引用(§5.1)。方法类型或方法句柄被解析(§5.4.3.5),对所产生的java.lang.invoke.MethodType或java.lang.invoke.MethodHandle实例的引用值被推入操作数栈。
链接异常情况
在解析一个类的符号引用时,任何与类解析有关的异常(§5.4.3.1)都可以被抛出。
在解析对方法类型或方法句柄的符号引用时,可以抛出任何与方法类型或方法句柄解析相关的异常(§5.4.3.5)。
笔记
ldc指令只能用于推送一个来自浮点值集的浮点类型的值(§2.3.2),因为常量池中的浮点类型的常量(§4.4.4)必须来自浮点值集。
ldc_w
运作
从运行时常量池中推送项目(宽索引)。
格式
ldc_w
indexbyte1
indexbyte2
表格
ldc_w = 19 (0x13)
操作数栈
…→
…,价值
描述
无符号的indexbyte1和indexbyte2被组合成一个无符号的16位索引进入当前类的运行时常量池(§2.6),其中索引的值被计算为(indexbyte1 << 8)| indexbyte2。索引必须是当前类的运行时常量池中的有效索引。索引处的运行时常量池条目必须是int或float类型的运行时常量,或对字符串字面的引用,或对类、方法类型或方法句柄的符号引用(§5.1)。
如果运行时常量池条目是int或float类型的运行时常量,则该运行时常量的数值将分别作为int或float推入操作数栈。
否则,如果运行时常量池条目是对代表字符串字面的String类实例的引用(§5.1),那么对该实例的引用value将被推入操作数栈。
否则,如果运行时常量池条目是对一个类的符号引用(§4.4.1)。被命名的类将被解析(§5.4.3.1),对代表该类的Class对象的引用,value,将被推入操作数栈。
否则,运行时常量池条目必须是对方法类型或方法柄的符号引用(§5.1)。方法类型或方法句柄被解析(§5.4.3.5),对所产生的java.lang.invoke.MethodType或java.lang.invoke.MethodHandle实例的引用值被推入操作数栈。
链接异常情况
在对一个类的符号引用的解析过程中,任何与类解析有关的异常(§5.4.3.1)都可以被抛出。
在解析对方法类型或方法句柄的符号引用时,可以抛出任何与方法类型或方法句柄解析相关的异常(§5.4.3.5)。
笔记
ldc_w指令与ldc指令(§ldc)相同,只是其运行时常量池索引更宽。
ldc_w指令只能用于推送一个来自浮点值集的浮点值(§2.3.2),因为常量池中的浮点值(§4.4.4)必须来自浮点值集。
ldc2_w
运作
从运行时常量池中推送长或双数(宽索引)。
格式
ldc2_w
indexbyte1
indexbyte2
表格
ldc2_w = 20 (0x14)
操作数栈
…→
…,价值
描述
无符号的indexbyte1和indexbyte2被组合成一个无符号的16位索引进入当前类的运行时常量池(§2.6),其中索引的值被计算为(indexbyte1 << 8)| indexbyte2。索引必须是当前类的运行时常量池中的有效索引。索引处的运行时常量池条目必须是一个long或double类型的运行时常量(§5.1)。该运行时常量的数值将分别作为long或double推入操作数栈。
笔记
只有ldc2_w指令的宽索引版本存在;没有ldc2指令可以推送单字节索引的长或双字节。
ldc2_w指令只能用来推送一个来自双倍值集(§2.3.2)的双倍值,因为常量池(§4.4.5)中的双倍常量必须来自双倍值集。
ldiv
运作
分割长
格式
ldiv
表格
ldiv = 109 (0x6d)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。这些值从操作数栈中弹出。long结果是Java编程语言表达式value1/value2的值。这个结果被推到操作数栈上。
长除法向0取整;也就是说,n/d中的长值产生的商是一个长值q,其大小尽可能大,同时满足|d⋅q| ≤|n|。此外,当|n|≥|d|且n和d的符号相同时,q为正,但当|n|≥|d|且n和d的符号相反时,q为负。
有一种特殊情况不符合这个规则:如果除数是长类型的最大可能的负整数,而除数是-1,那么就会发生溢出,结果等于除数;尽管有溢出,在这种情况下也不会抛出异常。
运行时异常
如果长除法中除数的值是0,ldv\会\抛出一个ArithmeticException。
lload
运作
从本地变量加载长字符串
格式
lload
索引
表格
lload = 22 (0x16)
操作数栈
…→
…,价值
描述
索引是一个无符号的字节。index和index+1都必须是当前帧的局部变量阵列的索引(§2.6)。索引处的局部变量必须包含一个long。索引处的局部变量的值被推到操作数栈上。
笔记
lload操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
lload_
运作
从本地变量加载长字符串
格式
lload_<n
表格
lload_0 = 30 (0x1e)
lload_1 = 31 (0x1f)
lload_2 = 32 (0x20)
lload_3 = 33 (0x21)
操作数栈
…→
…,价值
描述
< n>和< n>+1都必须是当前帧的局部变量阵列的索引(§2.6)。< n>处的局部变量必须包含一个long。< n>处的局部变量的值被推到操作数栈上。
笔记
每条lload_指令都与索引为的lload相同,只是操作数是隐含的。
lmul
运作
乘以长
格式
lmul
表格
lmul = 105 (0x69)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。这些值从操作数栈中弹出。long的结果是value1 value2。该结果被推到操作数堆栈中。
结果是真正的数学结果的64个低阶位,以足够宽的二补格式,表示为一个long类型的值。如果发生溢出,结果的符号可能与两个值的数学乘法的符号不一样。
尽管可能会发生溢出,但执行lmul指令时绝不会抛出一个运行时异常。
lneg
运作
否定长
格式
lneg
表格
lneg = 117 (0x75)
操作数栈
…,值→
…,结果
描述
该值必须是long类型。它被从操作数栈中弹出。长的结果是值的算术否定,-值。该结果被推到操作数栈中。
对于长值,否定与从零减去相同。因为Java虚拟机对整数使用二补表示法,而二补值的范围不是对称的,所以对最大负长的否定结果也是最大负数。尽管发生了溢出,但没有抛出异常。
对于所有长值x,-x等于(~x)+1。
lookupswitch
运作
通过按键匹配和跳转访问跳转表
格式
lookupswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
npairs1
npairs2
npairs3
npairs4
match-offset pairs….
表格
lookupswitch = 171 (0xab)
操作数栈
…,钥匙→
…
描述
lookupswitch是一条可变长度的指令。在lookupswitch操作码之后,必须有0到3个字节作为填充,这样defaultbyte1开始的地址是当前方法开始的4个字节的倍数(即其第一条指令的操作码)。在填充之后,紧接着是一系列有符号的32位数值:default、npairs,然后是npairs对有符号的32位数值。npairs必须大于或等于0。每个npairs对由一个int匹配和一个有符号的32位偏移组成。每个有符号的32位值是由四个无符号字节构成的,如(byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4。
lookupswitch指令的表匹配-偏移对必须按匹配的数字顺序递增排序。
键必须是int类型的,并从操作数栈中弹出。键被与匹配值进行比较。如果它等于其中一个值,那么通过将相应的偏移量加到这个lookupswitch指令的操作码的地址中来计算目标地址。如果键值与任何一个匹配值不匹配,目标地址的计算是将默认值加到这个lookupswitch指令的操作码的地址上。然后在目标地址继续执行。
从每个匹配-偏移对的偏移量可以计算出的目标地址,以及从默认计算出的目标地址,必须是包含这个查找开关指令的方法内的指令的操作码地址。
笔记
当且仅当包含查找开关的方法被定位在4字节边界上时,查找开关指令的4字节操作数的对齐要求保证了这些操作数的4字节对齐。
匹配-偏移对被排序,以支持比线性搜索更快的查找程序。
lor
运作
布尔型或长型
格式
lor
表格
lor = 129 (0x81)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。它们被从操作数堆栈中弹出。通过对value1和value2进行位数包容的OR,计算出一个长的结果。这个结果被推到操作数栈中。
lrem
运作
剩余部分长
格式
凌云
表格
lrem = 113 (0x71)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。这些值从操作数栈中弹出。long结果是value1 - (value1 / value2) value2。这个结果被推到操作数堆栈中。
lrem指令的结果是(a/b)b+(a%b)等于a。这一特性甚至在特殊情况下也成立,即分母是其类型中可能最大的负长,除数是-1(余数是0)。从这一规则可以看出,只有当红利为负数时,余数运算的结果才能为负数,只有当红利为正数时才能为正数;此外,结果的大小总是小于除数的大小。
运行时异常
如果长余运算符的除数值为0,lrem\会抛出一个ArithmeticException。
lreturn
运作
从方法中返回long
格式
返回
表格
lreturn = 173 (0xad)
操作数栈
…,值→
[空]
描述
当前方法的返回类型必须是long。值必须是long类型的。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。如果没有抛出异常,值会从当前帧的操作数堆栈中弹出(§2.6)并推到调用者的帧的操作数堆栈中。当前方法的操作数栈中的任何其他值都被丢弃了。
然后解释器将控制权返回给方法的调用者,恢复调用者的框架。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,那么lreturn\会\抛出一个IllegalMonitorStateException。这种情况可能会发生,例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果在调用当前方法的过程中违反了这些规则中的第一条,那么lreturn\会抛出一个IllegalMonitorStateException。
lshl
运作
左移长
格式
lshl
表格
lshl = 121 (0x79)
操作数栈
…, value1, value2 →
…,结果
描述
value1必须是long类型,value2必须是int类型。这些值从操作数栈中弹出。通过将value1左移s位来计算long结果,其中s是value2的低6位。这个结果被推到操作数栈上。
笔记
因此,实际使用的移位距离总是在0到63的范围内,包括在内,就像值2与掩码值0x3f进行了位逻辑和。
lshr
运作
算术右移长
格式
lshr
表格
lshr = 123 (0x7b)
操作数栈
…, value1, value2 →
…,结果
描述
value1必须是long类型,value2必须是int类型。这些值从操作数栈中弹出。通过将value1右移s位,加上符号扩展,计算出一个long结果,其中s是value2的低6位的值。这个结果被推到操作数栈上。
笔记
结果值是floor(value1 / 2s ),其中s是value2 & 0x3f。对于非负数的value1,这相当于截断长除法的2次方s。因此,实际使用的移位距离总是在0到63的范围内,包括在内,就像value2与掩码值0x3f进行了位逻辑和的处理。
lstore
运作
将长字符串存储到本地变量中
格式
lstore
索引
表格
lstore = 55 (0x37)
操作数栈
…,值→
…
描述
索引是一个无符号的字节。index和index+1都必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是long类型的。它被从操作数堆栈中弹出,位于index和index+1的局部变量被设置为值。
笔记
lstore操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
lstore_
运作
将长字符串存储到本地变量中
格式
lstore_<n
表格
lstore_0 = 63 (0x3f)
lstore_1 = 64 (0x40)
lstore_2 = 65 (0x41)
lstore_3 = 66 (0x42)
操作数栈
…,值→
…
描述
< n>和< n>+1都必须是当前帧的局部变量阵列的索引(§2.6)。操作数栈顶部的值必须是long类型的。它被从操作数栈中弹出,位于< n>和< n>+1的局部变量被设置为值。
笔记
每条lstore_指令都与索引为的lstore相同,只是操作数是隐含的。
lsub
运作
减去长
格式
列表
表格
lsub = 101 (0x65)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。这些值从操作数堆栈中被弹出。long的结果是value1 - value2。该结果被推到操作数堆栈中。
对于长减法,a-b产生的结果与a+(-b)相同。对于长值来说,从零减去的结果与否定是一样的。
结果是真正的数学结果的64个低阶位,以足够宽的二补格式,表示为一个long类型的值。如果发生溢出,那么结果的符号可能与两个值的数学差值的符号不一样。
尽管可能会发生溢出,但执行lsub指令永远不会抛出一个运行时异常。
lushr
运作
逻辑性右移长
格式
Lushr
表格
lushr = 125 (0x7d)
操作数栈
…, value1, value2 →
…,结果
描述
value1必须是long类型,value2必须是int类型。这些值从操作数栈中弹出。通过将value1的逻辑位置右移s位来计算long的结果,扩展为零,其中s是value2的低6位的值。这个结果被推到操作数栈上。
笔记
如果value1是正数,s是value2 & 0x3f,结果与value1 >> s相同;如果value1是负数,结果等于表达式的值(value1 >> s)+(2L << ~s)。(2L << ~s)项的增加抵消了传播的符号位。实际使用的移位距离总是在0到63的范围内,包括在内。
lxor
运作
Boolean XOR long
格式
lxor
表格
lxor = 131 (0x83)
操作数栈
…, value1, value2 →
…,结果
描述
value1和value2都必须是long类型。它们被从操作数堆栈中弹出。通过对value1和value2的位数排他性OR来计算一个long的结果。这个结果被推到操作数栈中。
监视者\
运作
输入对象的监视器
格式
monitorenter
表格
监视器 = 194 (0xc2)
操作数栈
…, objectref →
…
描述
objectref必须是引用类型。
每个对象都与一个监视器相关联。当且仅当它有一个所有者时,一个监视器被锁定。执行monitorenter的线程试图获得与objectref相关的监视器的所有权,如下所示。
· 如果与objectref相关的监视器的入口计数为0,线程就会进入该监视器并将其入口计数设置为1。然后,该线程就是该监视器的所有者。
· 如果线程已经拥有与objectref相关的监视器,它将重新进入该监视器,并增加其进入次数。
· 如果另一个线程已经拥有了与objectref相关的监视器,那么这个线程就会阻塞,直到监视器的条目数为零,然后再次尝试获得所有权。
运行时异常
如果objectref\为空,monitorenter\会\抛出一个NullPointerException。
笔记
monitorenter指令可与一个或多个monitorexit指令(§monitorexit)一起使用,以实现Java编程语言中的同步语句(§3.14)。monitorenter和monitorexit指令不用于同步方法的实现,尽管它们可以用来提供同等的锁定语义。Java虚拟机的方法调用和返回指令隐含地处理了调用同步方法时的监视器进入和返回时的监视器退出,就像使用monitorenter和monitorexit一样。
监视器与对象的关联可以用超出本规范范围的各种方式来管理。例如,监视器可以与对象同时被分配和取消。或者,它可以在线程试图获得对对象的独占访问时被动态分配,并在后来没有线程留在该对象的监视器中时被释放。
Java编程语言的同步构造需要支持对监视器的操作,除了进入和退出。这些操作包括在监视器上等待(Object.wait)和通知在监视器上等待的其他线程(Object.notifyAll和Object.notify)。这些操作在Java虚拟机提供的标准包java.lang中被支持。在Java虚拟机的指令集中没有出现对这些操作的明确支持。
监察局\
运作
退出对象的监控
格式
monitorexit
表格
monitorexit = 195 (0xc3)
操作数栈
…, objectref →
…
描述
objectref必须是引用类型。
执行monitorexit的线程必须是与objectref引用的实例相关的监视器的所有者。
线程递减与objectref相关的监视器的入口计数。如果结果是条目计数为零,那么该线程退出监视器,不再是其所有者。其他正在阻塞的线程可以尝试进入该监视器。
运行时异常情况
如果objectref\为空,monitorexit\会\抛出一个NullPointerException。
否则,如果执行monitorexit\的\线程不是与objectref\引用的\实例相关的监视器的所有者,monitorexit\会抛出一个IllegalMonitorStateException。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且如果这些规则中的第二条被这个monitorexit\指令的执行所违反,那么monitorexit\会\抛出一个IllegalMonitorStateException。
笔记
一个或多个monitorexit指令可与monitorenter指令(§monitorenter)一起使用,以实现Java编程语言中的同步语句(§3.14)。monitorenter和monitorexit指令不用于同步方法的实现,尽管它们可以用来提供同等的锁定语义。
Java虚拟机对同步方法和同步语句中抛出的异常有不同的支持。
· 正常同步方法完成时的监控退出是由Java虚拟机的返回指令处理的。在突然的同步方法完成时,监控器的退出是由Java虚拟机的atrow指令隐式处理的。
· 当异常从同步语句中抛出时,使用Java虚拟机的异常处理机制(§3.14)实现从执行同步语句之前进入的监视器中退出。
multianewarray
运作
创建新的多维数组
格式
多样化阵列
indexbyte1
indexbyte2
尺寸
表格
multianewarray = 197 (0xc5)
操作数栈
…, count1, [count2, …] →
…, arrayref
描述
dimensions操作数是一个无符号字节,必须大于或等于1,它代表要创建的数组的维数。操作数栈必须包含维数值。每个维度值都代表要创建的数组的维数,必须是int类型,并且必须是非负数。count1是第一个维度的期望长度,count2是第二个维度的期望长度,等等。
所有的计数值都从操作数栈中弹出。无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。索引中的运行时常量池项目必须是对类、数组或接口类型的符号引用。被命名的类、数组或接口类型被解析(§5.4.3.1)。结果条目必须是一个维度大于或等于维度的数组类类型。
一个新的数组类型的多维数组被从垃圾收集的堆中分配。如果任何一个计数值为0,就不分配后续维度。数组第一维的组件被初始化为第二维类型的子数组,以此类推。数组最后分配的维度的组件被初始化为数组类型的元素类型的默认初始值(§2.3,§2.4)。新数组的引用arrayref被推到操作数栈上。
链接异常情况
在解析对类、数组或接口类型的符号引用时,可以抛出§5.4.3.1中记载的任何异常。
否则,如果当前类没有权限访问已解决的数组类的元素类型,multianewarray\会\抛出一个IllegalAccessError。
运行时异常
否则,如果操作数堆栈中的任何一个\尺寸值\小于0,\多数组\指令就会抛出一个负数组大小异常。
笔记
在创建单维数组时,使用newarray或anewarray(§newarray,§anewarray)可能更有效率。
通过运行时常量池引用的数组类可能比multianewarray指令的dimensions操作数多。在这种情况下,只有数组的第一个维度被创建。
new
运作
创建新对象
格式
新的
indexbyte1
indexbyte2
表格
new = 187 (0xbb)
操作数栈
…→
…, objectref
描述
无符号的indexbyte1和indexbyte2用于构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。索引中的运行时常量池项目必须是对一个类或接口类型的符号引用。被命名的类或接口类型被解析(§5.4.3.1),并且应该产生一个类的类型。该类的新实例的内存从垃圾收集的堆中分配,新对象的实例变量被初始化为它们的默认初始值(§2.3, §2.4)。objectref,一个对实例的引用,被推到操作栈上。
在成功解析类的时候,如果它还没有被初始化,就会被初始化(§5.5)。
链接异常情况
在解析对类、数组或接口类型的符号引用时,可以抛出§5.4.3.1中记载的任何异常。
否则,如果对类、数组或接口类型的符号引用被解析为一个接口或一个抽象类,new\会\抛出一个实例化错误。
运行时异常
否则,如果这个\新\指令的执行导致被引用类的初始化,new\可能会抛出一个错误,详见JLS §15.9.4。
笔记
新指令并没有完全创建一个新的实例;直到实例初始化方法(§2.9)在未初始化的实例上被调用,实例创建才会完成。
newarray
运作
创建新的数组
格式
新数组
atype
表格
newarray = 188 (0xbc)
操作数栈
…,计数→
…, arrayref
描述
Count必须是int类型。它被从操作数栈中弹出。count代表要创建的数组中的元素数量。
atype是一个代码,表示要创建的数组的类型。它必须取以下值之一。
表6.5.newarray-A.阵列类型代码
阵列类型 | atype\ |
---|---|
T_BOOLEAN | 4 |
T_CHAR | 5 |
T_FLOAT | 6 |
T_DOUBLE | 7 |
T_BYTE | 8 |
T_SHORT | 9 |
T_INT | 10 |
T_LONG | 11 |
一个新的数组,其组件类型为type,长度为count,被从垃圾收集堆中分配。这个新数组对象的引用arrayref被推入操作数栈。新数组的每个元素都被初始化为数组类型的元素类型的默认初始值(§2.3, §2.4)。
运行时异常
如果count\小于0,newarray\会\抛出一个NegativeArraySizeException。
笔记
在Oracle的Java虚拟机实现中,布尔类型的数组(type为T_BOOLEAN)被存储为8位值的数组,并使用baload和bastore指令(§baload,§bastore)进行操作,这些指令也访问字节类型的数组。其他实现可以实现打包的布尔数组;baload和bastore指令仍然必须用来访问这些数组。
nop
运作
什么都不做
格式
没有
表格
nop = 0 (0x0)
操作数栈
无变化
描述
什么都不做。
pop
运作
弹出顶端操作数的堆栈值
格式
pop
表格
pop = 87 (0x57)
操作数栈
…,值→
…
描述
从操作数堆栈中弹出最高值。
除非value是第1类计算类型的值,否则不得使用pop指令(§2.11.1)。
pop2
运作
弹出顶部的一个或两个操作数的堆栈值
格式
pop2表格
pop2 = 88 (0x58)
操作数栈
表格1。
…, value2, value1 →
…
其中value1和value2各是一个第1类计算类型的值(§2.11.1)。
表格2。
…,值→
…
其中value是第2类计算类型的一个值(§2.11.1)。
描述
从操作数栈中弹出最上面的一个或两个值。
putfield
运作
在对象中设置字段
格式
场地
indexbyte1
indexbyte2
表格
Putfield = 181 (0xb5)
操作数栈
…, objectref, value →
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个字段的符号引用(§5.1),它给出了字段的名称和描述符,以及对要找到该字段的类的符号引用。objectref的类不能是一个数组。如果字段是受保护的,并且它是当前类的超类的成员,并且该字段不是在与当前类相同的运行时包(§5.3)中声明的,那么objectref的类必须是当前类或当前类的子类。
被引用的字段被解析(§5.4.3.2)。由putfield指令存储的值的类型必须与被引用字段的描述符兼容(§4.3.2)。如果字段描述符的类型是布尔值、字节、char、short或int,那么该值必须是int。如果字段描述符的类型是float、long或double,那么该值必须分别是float、long或double。如果字段描述符类型是一个引用类型,那么值必须是与字段描述符类型赋值兼容的类型(JLS §5.2)。如果字段是最终的,它必须在当前类中声明,并且该指令必须出现在当前类的实例初始化方法()中(§2.9)。
值和objectref被从操作数堆栈中弹出。objectref必须是引用类型。值经过值集转换(§2.8.3),产生值’,并且对象ref中的引用字段被设置为值’。
链接异常情况
在解析对字段的符号引用时,任何与字段解析有关的异常(§5.4.3.2)都可以被抛出。
否则,如果解决的字段是一个静态字段,putfield\会\抛出一个IncompatibleClassChangeError。
否则,如果该字段是最终的,它必须在当前类中声明,并且该指令必须出现在当前类的实例初始化方法()中。否则,会抛出一个IllegalAccessError。
运行时异常
否则,如果objectref\为空,putfield\指令会抛出一个NullPointerException。
putstatic
运作
设置类中的静态字段
格式
靜置
indexbyte1
indexbyte2
表格
putstatic = 179 (0xb3)
操作数栈
…,值→
…
描述
无符号的indexbyte1和indexbyte2被用来构建当前类的运行时常量池的索引(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。该索引中的运行时常量池项目必须是对一个字段的符号引用(§5.1),它给出了字段的名称和描述符,以及对要找到该字段的类或接口的符号引用。被引用的字段被解析(§5.4.3.2)。
在成功解析字段时,如果声明被解析字段的类或接口尚未被初始化,那么该类或接口将被初始化(§5.5)。
由putstatic指令存储的值的类型必须与被引用字段的描述符兼容(§4.3.2)。如果字段描述符的类型是boolean、byte、char、short或int,那么该值必须是int。如果字段描述符的类型是float、long或double,那么该值必须分别是float、long或double。如果字段描述符类型是一个引用类型,那么值必须是与字段描述符类型赋值兼容的类型(JLS §5.2)。如果字段是最终的,它必须在当前类中声明,并且该指令必须出现在当前类的方法中(§2.9)。
该值从操作数堆栈中弹出并进行值集转换(§2.8.3),结果是值’。类字段被设置为value’。
链接异常情况
在解析类或接口字段的符号引用时,可以抛出任何与字段解析有关的异常(§5.4.3.2)。
否则,如果解决的字段不是静态(类)字段或接口字段,putstatic\会抛出一个IncompatibleClassChangeError。
否则,如果该字段是最终的,它必须在当前类中声明,并且该指令必须出现在当前类的方法中。否则,会抛出一个IllegalAccessError。
运行时异常
否则,如果这个putstatic\指令的执行导致被引用的类或接口的初始化,putstatic\可能会抛出一个错误,详见§5.5。
笔记
putstatic指令只能用于在初始化一个接口字段时设置该字段的值。接口字段只能被分配一次,即在接口被初始化时执行接口变量初始化表达式(§5.5, JLS §9.3.1)。
ret
运作
从子程序返回
格式
检索
索引
表格
ret = 169 (0xa9)
操作数栈
无变化
描述
索引是一个在0到255之间的无符号字节,包括在内。当前帧(§2.6)中索引处的局部变量必须包含一个returnAddress类型的值。本地变量的内容被写入Java虚拟机的pc寄存器,并在那里继续执行。
笔记
请注意,jsr(§jsr)将地址推到操作数堆栈上,而ret从局部变量中获取地址。这种不对称性是故意的。
在Java SE 6之前Oracle对Java编程语言的编译器的实现中,ret指令与jsr和jsr_w指令(§jsr,§jsr_w)一起用于实现finally子句(§3.13,§4.10.2.5)。
ret指令不应该与返回指令(§return)混淆。返回指令将控制权从一个方法返回给它的调用者,而不向调用者传回任何值。
ret操作码可以和wide指令(§wide)一起使用,使用两字节的无符号索引访问一个局部变量。
return
运作
从方法中返回无效
格式
返回
表格
返回 = 177 (0xb1)
操作数栈
…→
[空]
描述
当前方法的返回类型必须是void。如果当前方法是一个同步方法,那么在调用该方法时进入或重新进入的监视器将被更新,并可能被退出,就像在当前线程中执行monitorexit指令(§monitorexit)一样。如果没有抛出异常,当前帧(§2.6)的操作数栈上的任何值都会被丢弃。
然后解释器将控制权返回给方法的调用者,恢复调用者的框架。
运行时异常情况
如果Java虚拟机实现没有执行§2.11.10中描述的结构化锁定规则,那么如果当前方法是一个同步方法,并且当前线程不是调用该方法时进入或重新进入的监视器的所有者,则\返回\抛出一个IllegalMonitorStateException。例如,如果一个同步方法包含一个monitorexit\指令,但没有monitorenter\指令,在该方法被同步的对象上,这种情况可能发生。
否则,如果Java虚拟机实现执行了§2.11.10中描述的关于结构化锁定的规则,并且在调用当前方法的过程中违反了这些规则中的第一条,那么\返回时将\抛出一个IllegalMonitorStateException。
saload
运作
从阵列中加载短信息
格式
抢劫
表格
saload = 53 (0x35)
操作数栈
…,数组ref,索引→
…,价值
描述
arrayref必须是引用类型,并且必须指向一个组件为short类型的数组。索引必须是int类型的。arrayref和index都从操作数栈中弹出。索引处的数组成分被检索出来,并被符号扩展为一个int值。这个值被推到操作数栈上。
运行时异常情况
如果arrayref\为空,saload\将\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,saload\指令会抛出ArrayIndexOutOfBoundsException。
ststore
运作
存储到短数组中
格式
淘宝网
表格
sastore = 86 (0x56)
操作数栈
…, arrayref, index, value →
…
描述
arrayref必须是引用类型,并且必须指向一个组件为short类型的数组。索引和值都必须是int类型。arrayref, index, 和value被从操作数栈中弹出。int值被截断为short,并存储为由index索引的数组的组成部分。
运行时异常情况
如果arrayref\为空,sastore\会\抛出一个NullPointerException。
否则,如果index\不在arrayref\引用的\数组的范围内,sastore\指令会抛出ArrayIndexOutOfBoundsException。
sipush
运作
推短
格式
sipush
字节1
字节2
表格
sipush = 17 (0x11)
操作数栈
…→
…,价值
描述
即时无符号的字节1和字节2的值被组合成一个中间短值,其中短值是(字节1<<8)|字节2。然后中间值被符号扩展为一个int值。该值被推到操作数堆栈中。
swap
运作
交换前两个操作数的堆栈值
格式
互换
表格
swap = 95 (0x5f)
操作数栈
…, value2, value1 →
…, value1, value2
描述
交换操作数栈上的前两个值。
除非value1和value2都是第1类计算类型的值,否则不得使用交换指令(§2.11.1)。
笔记
Java虚拟机没有提供对第二类计算类型的操作数实现交换的指令。
tableswitch
运作
通过索引和跳转访问跳转表
格式
tableswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
lowbyte1
lowbyte2
lowbyte3
lowbyte4
highbyte1
highbyte2
highbyte3
highbyte4
jump offsets…
表格
tableswitch = 170 (0xaa)
操作数栈
…,指数→
…
描述
表转换是一条可变长度的指令。在表转换操作码之后,必须有0到3个字节作为填充,这样defaultbyte1开始的地址是当前方法开始的4个字节的倍数(即其第一条指令的操作码)。填充之后是构成三个有符号的32位数值的字节:default、low和high。紧接着是构成一系列高-低+1有符号的32位偏移量的字节。低值必须小于或等于高值。高-低+1有符号的32位偏移量被视为一个基于0的跳转表。每个有符号的32位值被构造为(byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4。
索引必须是int类型的,并从操作数堆栈中弹出。如果index小于low或者index大于high,那么通过将default加到这个表转换指令的操作码的地址中来计算目标地址。否则,将提取跳转表中index-low位置的偏移。目标地址的计算方法是将该偏移量加到该表转换指令的操作码的地址上。然后在目标地址继续执行。
从每个跳转表偏移量可以计算出的目标地址,以及从默认值可以计算出的目标地址,必须是包含这个表转换指令的方法内的指令操作码的地址。
笔记
当且仅当包含表开关的方法在4字节边界上开始时,表开关指令的4字节操作数所需的对齐方式保证了这些操作数的4字节对齐。
wide
运作
用额外的字节扩展局部变量的索引
格式1
广泛
indexbyte1
indexbyte2
其中是iload, fload, aload, lload, dload, istore, fstore, astore, lstore, dstore, 或ret之一。
格式2
广泛
iinc
indexbyte1
indexbyte2
constbyte1
constbyte2
表格
宽 = 196 (0xc4)
操作数栈
与修改后的指令相同
描述
宽指令修改了另一条指令的行为。它有两种形式,取决于被修改的指令。宽指令的第一种形式是修改指令iload、fload、aload、lload、dload、istore、fstore、astore、lstore、dstore或ret(§iload、§fload、§aload、§lload、§dload、§istore、§fstore、§astore、§lstore、§dstore、§ret)之一。第二种形式只适用于iinc指令(§iinc)。
在这两种情况下,宽操作码本身在编译后的代码中被宽修改的指令的操作码所跟随。在这两种形式中,两个无符号字节indexbyte1和indexbyte2跟在修改的操作码后面,并被组合成一个16位无符号索引,指向当前帧中的一个局部变量(§2.6),其中索引的值是(indexbyte1 << 8)| indexbyte2。计算出的索引必须是当前帧的局部变量数组中的一个索引。当宽指令修改lload、dload、lstore或dstore指令时,计算出的索引(index+1)后面的索引也必须是本地变量数组中的索引。在第二种形式中,两个即时无符号字节constbyte1和constbyte2在代码流中跟随indexbyte1和indexbyte2。这些字节也被组合成一个有符号的16位常数,其中常数是(constbyte1 << 8)| constbyte2。
加宽的字节码的操作和正常一样,除了使用更宽的索引,在第二种形式的情况下,还有更大的增量范围。
笔记
虽然我们说宽指令 “修改了另一条指令的行为”,但宽指令有效地将构成修改后的指令的字节作为操作数,在这个过程中否定了嵌入指令。在修改后的iinc指令中,iinc的逻辑操作数之一甚至不在操作码的正常偏移处。嵌入式指令决不能直接执行;其操作码决不能成为任何控制转移指令的目标。
第7章 操作码记忆法(Opcode Mnemonics)
本章给出了从Java虚拟机指令操作码,包括保留的操作码(§6.2),到这些操作码所代表的指令的记忆法的映射。
在Java SE 7之前,操作码值186没有被使用。
00
|
(0x00)
|
nop |
---|---|---|
01
|
(0x01)
|
aconst_null |
02
|
(0x02)
|
iconst_m1 |
03
|
(0x03)
|
iconst_0 |
04
|
(0x04)
|
iconst_1 |
05
|
(0x05)
|
iconst_2 |
06
|
(0x06)
|
iconst_3 |
07
|
(0x07)
|
iconst_4 |
08
|
(0x08)
|
iconst_5 |
09
|
(0x09)
|
lconst_0 |
10
|
(0x0a)
|
lconst_1 |
11
|
(0x0b)
|
fconst_0 |
12
|
(0x0c)
|
fconst_1 |
13
|
(0x0d)
|
fconst_2 |
14
|
(0x0e)
|
dconst_0 |
15
|
(0x0f)
|
dconst_1 |
16
|
(0x10)
|
bipush |
17
|
(0x11)
|
sipush |
18
|
(0x12)
|
ldc |
19
|
(0x13)
|
ldc_w |
20
|
(0x14)
|
ldc2_w |
21
|
(0x15)
|
iload |
22
|
(0x16)
|
lload |
23
|
(0x17)
|
fload |
24
|
(0x18)
|
dload |
25
|
(0x19)
|
aload |
26
|
(0x1a)
|
iload_0 |
27
|
(0x1b)
|
iload_1 |
28
|
(0x1c)
|
iload_2 |
29
|
(0x1d)
|
iload_3 |
30
|
(0x1e)
|
lload_0 |
31
|
(0x1f)
|
lload_1 |
32
|
(0x20)
|
lload_2 |
33
|
(0x21)
|
lload_3 |
34
|
(0x22)
|
fload_0 |
35
|
(0x23)
|
fload_1 |
36
|
(0x24)
|
fload_2 |
37
|
(0x25)
|
fload_3 |
38
|
(0x26)
|
dload_0 |
39
|
(0x27)
|
dload_1 |
40
|
(0x28)
|
dload_2 |
41
|
(0x29)
|
dload_3 |
42
|
(0x2a)
|
aload_0 |
43
|
(0x2b)
|
aload_1 |
44
|
(0x2c)
|
aload_2 |
45
|
(0x2d)
|
aload_3 |
46
|
(0x2e)
|
iaload |
47
|
(0x2f)
|
laload |
48
|
(0x30)
|
faload |
49
|
(0x31)
|
daload |
50
|
(0x32)
|
aaload |
51
|
(0x33)
|
baload |
52
|
(0x34)
|
caload |
53
|
(0x35)
|
saload |
54
|
(0x36)
|
istore |
55
|
(0x37)
|
lstore |
56
|
(0x38)
|
fstore |
57
|
(0x39)
|
dstore |
58
|
(0x3a)
|
astore |
59
|
(0x3b)
|
istore_0 |
60
|
(0x3c)
|
istore_1 |
61
|
(0x3d)
|
istore_2 |
62
|
(0x3e)
|
istore_3 |
63
|
(0x3f)
|
lstore_0 |
64
|
(0x40)
|
lstore_1 |
65
|
(0x41)
|
lstore_2 |
66
|
(0x42)
|
lstore_3 |
67
|
(0x43)
|
fstore_0 |
68
|
(0x44)
|
fstore_1 |
69
|
(0x45)
|
fstore_2 |
70
|
(0x46)
|
fstore_3 |
71
|
(0x47)
|
dstore_0 |
72
|
(0x48)
|
dstore_1 |
73
|
(0x49)
|
dstore_2 |
74
|
(0x4a)
|
dstore_3 |
75
|
(0x4b)
|
astore_0 |
76
|
(0x4c)
|
astore_1 |
77
|
(0x4d)
|
astore_2 |
78
|
(0x4e)
|
astore_3 |
79
|
(0x4f)
|
iastore |
80
|
(0x50)
|
lastore |
81
|
(0x51)
|
fastore |
82
|
(0x52)
|
dastore |
83
|
(0x53)
|
aastore |
84
|
(0x54)
|
bastore |
85
|
(0x55)
|
castore |
86
|
(0x56)
|
sastore |
87
|
(0x57)
|
pop |
88
|
(0x58)
|
pop2 |
89
|
(0x59)
|
dup |
90
|
(0x5a)
|
dup_x1 |
91
|
(0x5b)
|
dup_x2 |
92
|
(0x5c)
|
dup2 |
93
|
(0x5d)
|
dup2_x1 |
94
|
(0x5e)
|
dup2_x2 |
95
|
(0x5f)
|
swap |
96
|
(0x60)
|
iadd |
97
|
(0x61)
|
ladd |
98
|
(0x62)
|
fadd |
99
|
(0x63)
|
dadd |
100
|
(0x64)
|
isub |
101
|
(0x65)
|
lsub |
102
|
(0x66)
|
fsub |
103
|
(0x67)
|
dsub |
104
|
(0x68)
|
imul |
105
|
(0x69)
|
lmul |
106
|
(0x6a)
|
fmul |
107
|
(0x6b)
|
dmul |
108
|
(0x6c)
|
idiv |
109
|
(0x6d)
|
ldiv |
110
|
(0x6e)
|
fdiv |
111
|
(0x6f)
|
ddiv |
112
|
(0x70)
|
irem |
113
|
(0x71)
|
lrem |
114
|
(0x72)
|
frem |
115
|
(0x73)
|
drem |
116
|
(0x74)
|
ineg |
117
|
(0x75)
|
lneg |
118
|
(0x76)
|
fneg |
119
|
(0x77)
|
dneg |
120
|
(0x78)
|
ishl |
121
|
(0x79)
|
lshl |
122
|
(0x7a)
|
ishr |
123
|
(0x7b)
|
lshr |
124
|
(0x7c)
|
iushr |
125
|
(0x7d)
|
lushr |
126
|
(0x7e)
|
iand |
127
|
(0x7f)
|
land |
128
|
(0x80)
|
ior |
129
|
(0x81)
|
lor |
130
|
(0x82)
|
ixor |
131
|
(0x83)
|
lxor |
132
|
(0x84)
|
iinc |
133
|
(0x85)
|
i2l |
134
|
(0x86)
|
i2f |
135
|
(0x87)
|
i2d |
136
|
(0x88)
|
l2i |
137
|
(0x89)
|
l2f |
138
|
(0x8a)
|
l2d |
139
|
(0x8b)
|
f2i |
140
|
(0x8c)
|
f2l |
141
|
(0x8d)
|
f2d |
142
|
(0x8e)
|
d2i |
143
|
(0x8f)
|
d2l |
144
|
(0x90)
|
d2f |
145
|
(0x91)
|
i2b |
146
|
(0x92)
|
i2c |
147
|
(0x93)
|
i2s |
148
|
(0x94)
|
lcmp |
149
|
(0x95)
|
fcmpl |
150
|
(0x96)
|
fcmpg |
151
|
(0x97)
|
dcmpl |
152
|
(0x98)
|
dcmpg |
153
|
(0x99)
|
ifeq |
154
|
(0x9a)
|
ifne |
155
|
(0x9b)
|
iflt |
156
|
(0x9c)
|
ifge |
157
|
(0x9d)
|
ifgt |
158
|
(0x9e)
|
ifle |
159
|
(0x9f)
|
if_icmpeq |
160
|
(0xa0)
|
if_icmpne |
161
|
(0xa1)
|
if_icmplt |
162
|
(0xa2)
|
if_icmpge |
163
|
(0xa3)
|
if_icmpgt |
164
|
(0xa4)
|
if_icmple |
165
|
(0xa5)
|
if_acmpeq |
166
|
(0xa6)
|
if_acmpne |
167
|
(0xa7)
|
goto |
168
|
(0xa8)
|
jsr |
169
|
(0xa9)
|
ret |
170
|
(0xaa)
|
tableswitch |
171
|
(0xab)
|
lookupswitch |
172
|
(0xac)
|
ireturn |
173
|
(0xad)
|
lreturn |
174
|
(0xae)
|
freturn |
175
|
(0xaf)
|
dreturn |
176
|
(0xb0)
|
areturn |
177
|
(0xb1)
|
return |
178
|
(0xb2)
|
getstatic |
179
|
(0xb3)
|
putstatic |
180
|
(0xb4)
|
getfield |
181
|
(0xb5)
|
putfield |
182
|
(0xb6)
|
invokevirtual |
183
|
(0xb7)
|
invokespecial |
184
|
(0xb8)
|
invokestatic |
185
|
(0xb9)
|
invokeinterface |
186
|
(0xba)
|
invokedynamic |
187
|
(0xbb)
|
new |
188
|
(0xbc)
|
newarray |
189
|
(0xbd)
|
anewarray |
190
|
(0xbe)
|
arraylength |
191
|
(0xbf)
|
athrow |
192
|
(0xc0)
|
checkcast |
193
|
(0xc1)
|
instanceof |
194
|
(0xc2)
|
monitorenter |
195
|
(0xc3)
|
monitorexit |
196
|
(0xc4)
|
wide |
197
|
(0xc5)
|
multianewarray |
198
|
(0xc6)
|
ifnull |
199
|
(0xc7)
|
ifnonnull |
200
|
(0xc8)
|
goto_w |
201
|
(0xc9)
|
jsr_w |
202
|
(0xca)
|
breakpoint |
254
|
(0xfe)
|
impdep1 |
255
|
(0xff)
|
impdep2 |
jvm虚拟机规范 紧接上文的相关推荐
- 卸载虚拟机出现用户已存在的错误_极限 JVM (1) 虚拟机规范
基本上,业内对 JVM 的理解就源自于一本书-- <深入理解 JAVA 虚拟机> 谁没事就真的上手调优啊?基本所有的中文博客要么是理解了这本书的小结要么是错误地理解了这本书的小结. 整理一 ...
- java虚拟机规范这本书怎么样_JVM规范系列开篇:为什么要读JVM规范?
博主个人独立站点开通啦!欢迎点击访问:https://shuyi.tech 许多人知道类加载机制.JVM内存模型,但他们可能不知道什么是<Java虚拟机规范>.对于Java开发来说,< ...
- 【Java虚拟机规范】JVM类加载机制
[Java虚拟机规范]JVM类加载机制 理论知识 一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading).验证(Verification).准备(Prep ...
- JVM(一)JVM虚拟机内存结构 和 JAVA内存模型(JMM)
本文转自:浅析java内存模型--JMM(Java Memory Model) - 路易小七 - 博客园,尊重作者,转载请注明出处~ JVM虚拟机内存结构 和 JAVA内存模型 是两个不同的概念 JV ...
- 接口多个实现类加载哪个_深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现
本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...
- 深入理解JVM虚拟机6:深入理解JVM类加载机制
深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...
- JVM——虚拟机执行子系统
摘要 本文将深入的学习与分析JVM虚拟机的原理和相关的调优的相关实例. 虚拟机执行子系统 代码编译的结果从本地机器码转变为字节码, 是存储格式发展的一小步, 却是编程语言发展的一大步.记得在第一节计算 ...
- java 虚拟机规范_Java虚拟机规范----Java虚拟机结构
Java体系和一些基本概念 Java平台的结构图: JVM与JRE.JDK关系? JVM:Java Virtual Machine(Java虚拟机),负责执行符合规范的Class文件 JRE: Jav ...
- java一个接口执行结束释放内存_java的灵魂--JVM虚拟机
JVM是运行在操作系统之上的,它与硬件没有直接的交互 JVM体系结构 1.类加载器 负责加载class文件,class文件在文件开头有特定的文件标示, 并且ClassLoader只负责class文件的 ...
最新文章
- Application summary please elaborate on how you plan to use our API
- 避无可避:Mesos安全问题的几点思考
- mysql count or null_sql 语句中count()有条件的时候为什么要加上or null
- 软件构架实践_阅读笔记01(1-3)
- 智能会议系统(35)---深入浅出sip协议
- 阿里总裁马云对于第5个经济体技术有着独特的见解
- Java内存模型深度解析:重排序
- 安徽工业大学java实验报告_安徽工业大学java实验报告.doc
- 求字符串中ASCII码值最大和ASCII码值最小的字符
- 申请百度云文字识别OCR
- Python常用软件包,python使用的软件
- TotalCommander常用操作
- python凯撒密码实验报告_凯撒密码的python实现
- 【网络学习】对TortoiseSVN的基本了解及简单操作
- 2022聚合工艺操作证考试题模拟考试平台操作
- 【Encoder-Decoder】
- Swift基础——数组Array
- Android阿面试积累,android项目开发实战密码
- 公司监事会的职责具体是什么
- vivoX80Pro和华为P50Pro哪个值得入手参数对比