JVM规范研读-2
来源:互联网 发布:报修系统 源码 编辑:程序博客网 时间:2024/05/16 06:05
接着上一篇点击打开链接
4 JVM虚拟机编译
指令的格式:
<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
<index>是 code[]数组中的指令的操作码的索引,此处的 code[]数组就是存储当前方法的Java 虚拟机字节码的 Code 属性中的code[]数组。也可以认为<index>是相对于方法起始处的字节偏移量。<opcode>为指令的操作码的助记符号,<operandN>是指令的操作数,一条指令可以有 0 至多个操作数。<comment>为行尾的语法注释,譬如:
8 bipush 100 // Push int constant 100
举个栗子
spin()是一个很简单的方法,它进行了 100 次空循环:
编译后代码如下:
void spin() { int i;
for (i = 0; i < 100; i++) { ; // Loop body is empty
}}
Method void spin() 0 iconst_0 // Push int constant 0 1 istore_1 // Store into local variable 1 (i=0) 2 goto 8 // First time through don’t increment 5 iinc 1 1 // Increment local variable 1 by 1 (i++) 8 iload_1 // Push local variable 1 (i) 9 bipush 100 // Push int constant 100 11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done
spin()方法中,0 和 100 两个常量分别使用了两条不同的指令压入操作数栈。对于 0 采用了iconst_0 指令,它属于 iconst_<i>指令族。而对于 100 采用 bipush 指令,这条指令会获取它的立即操作数(Immediate Operand)1压入到操作数栈中。 spin()方法的第一个局部变量的传输过程由 istore_1 和 iload_1 指令完成,这两条指令都隐式指明了是对于第一个局部变量进行操作。istore_1 指令作用是从操作数栈中弹出一个 int 型的值,并保存在第一个局部变量中。iload_1 指令作用是将第一个局部变量的值压入操作数栈。
再举个栗子,不是整型
void dspin() { double i;
for (i = 0.0; i < 100.0; i++) { ; // Loop body is empty
}}
Method void dspin() 0 dconst_0 // Push double constant 0.0 1 dstore_1 // Store into local variables 1 and 2 2 goto 9 // First time through don’t increment 5 dload_1 // Push local variables 1 and 2 6 dconst_1 // Push double constant 1.0 7 dadd // Add; there is no dinc instruction 8 dstore_1 // Store result in local variables 1 and 2 9 dload_1 // Push local variables 1 and 2 10 ldc2_w #4 // Push double constant 100.0 13 dcmpg // There is no if_dcmplt instruction 14 iflt 5 // Compare and loop if less than (i < 100.0)
17 return // Return void when done
double类型占两个slot
double doubleLocals(double d1, double d2) { return d1 + d2;
}
Method double doubleLocals(double,double) 0 dload_1 // First argument in local variables 1 and 2 1 dload_3 // Second argument in local variables 3 and 4 2 dadd 3 dreturn
再举个栗子,将short提升为int
void sspin() { short i;
for (i = 0; i < 100; i++) { ; // Loop body is empty
}}
Java 虚拟机编译时要将其他可安全转化为 int 类型的数据转换为 int 类型进行操作。在将short 类型值转换为 int 类型值时,可以保证 short 类型值操作后结果一定在 int 类型的精度范围之内,因此 sspin()的编译后代码如下:
Method void sspin() 0 iconst_0 1 istore_1 2 goto 10
5 iload_1 // The short is treated as though an int 6 iconst_1 7 iadd 8 i2s // Truncate int to short
9 istore_1 10 iload_1 11 bipush 100 13 if_icmplt 5 16 return
4.2 运算
int align2grain(int i, int grain) { return ((i + grain-1) & ~(grain-1));
}
算术运算使用到的操作数都是从操作数栈中弹出的,运算结果被压回操作数栈中。在内部运算时,中间运算(Arithmetic Subcomputations)的结果可以被当作操作数使用。譬如~(grain-1)的值就是被这样使用的:
首先,grain−1的结果由第2个局部变量和立即操作数int数值1的计算得出。参与运算的操作数从操作数栈中弹出,然后它们将被改变,最后再入栈到操作数栈之中。这里被改变是单个操作数的算术指令ixor运算的结果(因为~x == −1^x)。类似地,ixor指令的结果是接下来将作为iand指令的操作数被使用。
整个方法的编译代码如下:
5 iload_2 // Push grain6 iconst_1 // Push int constant 17 isub // Subtract; push result8 iconst_m1 // Push int constant −19 ixor // Do XOR; push result
Method int align2grain(int,int) 0 iload_1 1 iload_2 2 iadd
3 iconst_1 4 isub 5 iload_2 6 iconst_1 7 isub
8 iconst_m1 9 ixor 10 iand 11 ireturn
4.3 访问常量池
ldc 和ldc_w指令用于访问运行时常量池中的对象,包括 String实例,但不包括double和long类型的值。当使用的运行时常量池的项的数目过多时(多于256个,1个字节能表示的范围),需要使用ldc_w指令取代ldc指令来访问常量池。ldc2_w指令用于访问类型为double和long的运行时常量池项。
void useManyNumeric() { int i = 100;
int j = 1000000; long l1 = 1; long l2 = 0xffffffff; double d = 2.2; ...do some calculations...
}
编译后代码如下:
Method void useManyNumeric()0 bipush 100 // Push a small int with bipush2 istore_13 ldc #1 // Push int constant 1000000; a larger int// value uses ldc5 istore_26 lconst_1 // A tiny long value uses short, fast lconst_17 lstore_38 ldc2_w #6 // Push long 0xffffffff (that is, an int −1); any// long constant value can be pushed using ldc2_w11 lstore 513 ldc2_w #8 // Push double constant 2.200000; uncommon// double values are also pushed using ldc2_w16 dstore 7...do those calculations...
4.4 条件控制语句
void useManyNumeric() { int i = 100;
int j = 1000000; long l1 = 1; long l2 = 0xffffffff; double d = 2.2; ...do some calculations...
}
编译后代码如下:
Method void useManyNumeric()0 bipush 100 // Push a small int with bipush2 istore_13 ldc #1 // Push int constant 1000000; a larger int// value uses ldc5 istore_26 lconst_1 // A tiny long value uses short, fast lconst_17 lstore_38 ldc2_w #6 // Push long 0xffffffff (that is, an int −1); any// long constant value can be pushed using ldc2_w11 lstore 513 ldc2_w #8 // Push double constant 2.200000; uncommon// double values are also pushed using ldc2_w16 dstore 7...do those calculations...
4.5 方法中的参数
int addTwo(int i, int j) { return i + j;
}
Method int addTwo(int,int)0 iload_1 // Push value of local variable 1 (i)1 iload_2 // Push value of local variable 2 (j)2 iadd // Add; leave int result on operand stack3 ireturn // Return int result
实例方法需要传递一个自身实例的引用作为第 0个局部变量。在Java语言中自身实例可以通过this关键字来访问。
类(static)方法不需要传递实例引用,所以它们不需要使用第0个局部变量来保存this关键字。如果addTwo()是类方法,那么接收的参数和之前示例相比略有不同:
static int addTwoStatic(int i, int j) { return i + j;
}
Method int addTwoStatic(int,int)0 iload_01 iload_12 iadd
3 ireturn
4.6 方法调用
对普通实例方法调用是在运行时根据对象类型进行分派的(相当于在 C++中所说的“虚方法”), 通过调用invokevirtual指令实现,每条 invokevirtual指令都会带有一个表示索引的参数,运行时常量池在该索引处的项为某个方法的符号引用,这个符号引用可以提供方法所在对象的类型的内部二进制名称、方法名称和方法描述符 。
举个栗子
int add12and13() { return addTwo(12, 13);
}
Method int add12and13()
0 aload_0 // Push local variable 0 (this)
1 bipush 12 // Push int constant 12
3 bipush 13 // Push int constant 13
5 invokevirtual #4 // Method Example.addtwo(II)I
8 ireturn // Return int on top of operand stack; it is the int result of addTwo()
第一步是将当前实例的自身引用“this”压入到操作数栈中。传递给方法的参数,int值12和13随后入栈。当调用addTwo()方法时,Java虚拟机会创建一个新的栈帧,传递给addTwo()方法的参数作为新的帧的对应局部变量的初始值。即this和两个传递给的addTwo()方法的参数12和13被作为addTwo()方法栈帧的第0、1、2个局部变量。 当addTwo()方法执行结束、方法返回时,int型的返回值被压入方法调用者的栈帧的操作数栈,即add12and13()方法的操作数栈中。这样addTwo()方法的返回值就被放置在调用者add12and13()方法的代码可以立刻使用到的地方。add12and13()方法的返回过程由add12and13()方法中的ireturn指令实现。由于调用的addTwo()方法返回的int值被压入当前操作数栈的栈顶,ireturn指令将会把当前操作数栈的栈顶值(此处就是addTwo()的返回值)压入调用add12and13()方法的操作数栈。然后跳转至调用者调用方法的下一条指令继续执行,并将调用者的栈帧重新设为当前栈帧。
如果方法中,调用了static方法
int add12and13() { return addTwoStatic(12, 13);
}
Method int add12and13()0 bipush 122 bipush 134 invokestatic #3 // Method Example.addTwoStatic(II)I7 ireturn
invokespecial 指令用于调用实例初始化方法(参见§3.8“使用类实例”),它也可以用来调用父类方法和私有方法。譬如下面例子中的 Near 和Far 两个类:
int add12and13() { return addTwoStatic(12, 13);
}
Method int add12and13()0 bipush 122 bipush 134 invokestatic #3 // Method Example.addTwoStatic(II)I7 ireturn
class Near { int it;
public int getItNear() { return getIt();
} private int getIt() {
return it;
}}
class Far extends Near { int getItFar() {
return super.getItNear();
}}
调用类 Near的方法 getItNear()(调用私有方法)被编译为:
Method int getItNear()0 aload_01 invokespecial #5 // Method Near.getIt()I4 ireturn
调用类Far的方法getItFar ()(调用父类方法)被编译为:
Method int getItFar()0 aload_01 invokespecial #4 // Method Near.getItNear()I4 ireturn
请注意,所有使用 invokespecial指令调用的方法都需要 this作为第一个参数,保存在第一个局部变量之中。
编译器会先把一个方法所在的对象的引用压入操作数栈,方法参数则按顺序跟随这个对象之后入栈。编译器在生成invokevirtual指令时,也会生成这条指令所引用的描述符,这个描述符提供了方法参数和返回值的信息。作为方法解析时的一个特殊处理过程,一个用于调用java.lang.invoke.MethodHandle的 invoke()或者invokeExact()方法的invokevirtual指令会提供一个方法描述符,这个方法描述符符合语法规则,并且在描述符中确定的类型信息将会被解析。
4.7 初始化init,cinit
Object create() { return new Object();
}
Method java.lang.Object create() 0 new #1 // Class java.lang.Object 3 dup 4 invokespecial #4 // Method java.lang.Object.<init>()V
7 areturn
对象引用reference类型也有它自己类型专有的 Java虚拟机指令,譬如:
int i; // An instance variableMyObj example() {
MyObj o = new MyObj();
return silly(o);}
MyObj silly(MyObj o) { if (o != null) { return o;
} else { return o;
}}
编译后代码如下:
Method MyObj example()0 new #2 // Class MyObj3 dup4 invokespecial #5 // Method MyObj.<init>()V7 astore_18 aload_09 aload_110 invokevirtual #4 // Method Example.silly(LMyObj;)LMyObj;13 areturn
Method MyObj silly(MyObj)0 aload_11 ifnull 64 aload_1
5 areturn6 aload_17 areturn
实例变量的访问:类实例的字段(实例变量)将使用getfield 和putfield 指令进行访问,假设i 是一个int型的实例变量,且方法 getIt()和setIt()的定义如下:
void setIt(int value) { i = value;编译后代码如下:
Method void setIt(int)0 aload_01 iload_12 putfield #4 // Field Example.i I5 return
Method int getIt()0 aload_01 getfield #4 // Field Example.i I4 ireturn
4.8 数组
void createBuffer() { int buffer[];
int bufsz = 100; int value = 12; buffer = new int[bufsz]; buffer[10] = value; value = buffer[11];
}
编译后代码如下:
Method void createBuffer()0 bipush 100 // Push int constant 100 (bufsz)2 istore_2 // Store bufsz in local variable 23 bipush 12 // Push int constant 12 (value)5 istore_3 // Store value in local variable 3
6 iload_2 // Push bufsz...
7 newarray int // ...and create new array of int of that length9 astore_1 // Store new array in buffer
10 aload_1 // Push buffer
11 bipush 10 // Push int constant 10
13 iload_3 // Push value
14 iastore // Store value at buffer[10]
15 aload_1 // Push buffer
16 bipush 11 // Push int constant 11
18 iaload // Push value at buffer[11]...
19 istore_3 // ...and store it in value
20 return
anewarray指令用于创建元素为引用类型的一维数组。譬如:
编译后代码如下:
void createThreadArray() { Thread threads[]; int count = 10; threads = new Thread[count]; threads[0] = new Thread();
}
Method void createThreadArray()
0 bipush 10 // Push int constant 10
2 istore_2 // Initialize count to that
3 iload_2 // Push count, used by anewarray
4 anewarray class #1 // Create new array of class Thread
7 astore_1 // Store new array in threads
8 aload_1 // Push value of threads
9 iconst_0 // Push int constant 0
10 new #1 // Create instance of class Thread
13 dup // Make duplicate reference...
14 invokespecial #5 // ...to pass to instance initialization method// Method java.lang.Thread.<init>()V
17 aastore // Store new Thread in array at 0
18 return
anewarray 指令也可以用于创建多维数组的第一维。不过我们也可以选择采用multianewarray指令一次性创建多维数组。譬如三维数组:
int[][][] create3DArray() { int grid[][][]; grid = new int[10][5][];
第70页/共387页
Java虚拟机规范 — 第3章JAVA虚拟机编译器
return grid;}
编译后代码如下:
Method int create3DArray()[][][]
0 bipush 10 // Push int 10 (dimension one)2 iconst_5 // Push int 5 (dimension two)3 multianewarray #1 dim #2 // Class [[[I, a three
// dimensional int array;
// only create first two
// dimensions
7 astore_1
8 aload_1
9 areturn
// Store new array...// ...then prepare to return it
4.9 分支语句
int chooseNear(int i) { switch (i) {
case 0: return 0;case 1: return 1;case 2: return 2;default: return -1;
}}
编译后
Method int chooseNear(int)0 iload_1 // Push local variable 1 (argument i)1 tableswitch 0 to 2: // Valid indices are 0 through 20: 28 // If i is 0, continue at 281: 30 // If i is 1, continue at 302: 32 // If i is 2, continue at 32default:34 // Otherwise, continue at 3428 iconst_0 // i was 0; push int constant 0...29 ireturn // ...and return it30 iconst_1 // i was 1; push int constant 1...31 ireturn // ...and return it32 iconst_2 // i was 2; push int constant 2...33 ireturn // ...and return it34 iconst_m1 // otherwise push int constant –1...35 ireturn // ...and return it
当 switch语句中的 case分支的条件值比较稀疏时,tableswitch指令的空间使用率偏低。这种情况下将使用 lookupswitch指令来替代。
int chooseFar(int i) { switch (i) {
case -100: return -1; case 0: return 0; case 100: return 1;
default: return -1;
}}
编译后
Method int chooseFar(int)
0 iload_11 lookupswitch 3:−100: 360: 38100: 40default:4236 iconst_m137 ireturn38 iconst_039 ireturn40 iconst_141 ireturn42 iconst_m143 ireturn
4.10 操作数栈
public long nextIndex() {return index++;}private long index = 0;
Method long nextIndex()0 aload_0 // Push this1 dup // Make a copy of it2 getfield #4 // One of the copies of this is consumed// pushing long field index,
// above the original this
5 dup2_x1 // The long on top of the operand stack is
// inserted into the operand stack below the// original this6 lconst_1 // Push long constant 17 ladd // The index value is incremented...8 putfield #4 // ...and the result stored back in the field11 lreturn // The original value of index is left on
// top of the operand stack, ready to be returned
4.11 异常
void cantBeZero(int i) throws TestExc { if (i == 0) {
throw new TestExc();
}}
Method void cantBeZero(int)0 iload_1 // Push argument 1 (i)1 ifne 12 // If i==0, allocate instance and throw4 new #1 // Create instance of TestExc7 dup // One reference goes to the constructor8 invokespecial #7 // Method TestExc.<init>()V11 athrow // Second reference is thrown12 return // Never get here if we threw TestExc
try-catch语句
void catchOne() { try {
tryItOut(); } catch (TestExc e) {
handleExc(e);
}
}
编译后代码如下:
Method void catchOne()
0 aload_0 // Beginning of try block
1 invokevirtual #6 // Method Example.tryItOut()V
4 return // End of try block; normal return
5 astore_1 // Store thrown value in local variable 16 aload_0 // Push this
7 aload_1 // Push thrown value
8 invokevirtual #5 // Invoke handler method:
// Example.handleExc(LTestExc;)V
11 return // Return after handling TestExc
Exception table:
From To Target Type
0 4 5 Class TestExc
异常表:
处理范围包括 from但不包括 to所表示的偏移量本身
4.12 Finally
void tryFinally() { try {
tryItOut(); } finally {
wrapItUp();
}
}
很早之前(JDK 1.4.2之前)的 Sun Javac已经不再为 finally语句生成 jsr和 ret指令了,而是改为在每个分支之后冗余代码的形式来实现 finally语句,所以在这节开头作者需要特别说明。在版本号为51.0(JDK 7的Class文件)的Class文件中,甚至还明确禁止了指令流中出现jsr、jsr_w指令。
Oracle 的1.0.2 版本 JDK的 javac编译器生成的代码如下:
Method void tryFinally()
0 aload_0 // Beginning of try block
1 invokevirtual #6 // Method Example.tryItOut()V
4 jsr 14 // Call finally block
7 return // End of try block
8 astore_1 // Beginning of handler for any throw
9 jsr 14 // Call finally block
12 aload_1 // Push thrown value
13 athrow // ...and rethrow the value to the invoker14 astore_2 // Beginning of finally block
15 aload_0 // Push this
16 invokevirtual #5 // Method Example.wrapItUp()V
19 ret 2 // Return from finally block
Exception table:
From To Target Type
0 48 any
“jsr 14”表示的意思是“调用程序子片段(Subroutine Call)”,这条指令使程序跳转至第14 句的finally 语句块
当 finally语句块运行结束,使用“ret 2”指令将程序返回至jsr 指令(即第 4句)的下一句继续执行。
4.13 同步语句
void onlyMe(Foo f) { synchronized(f) {
doSomething(); }
}
编译后代码如下:
Method void onlyMe(Foo)0 aload_1 // Push f1 dup // Duplicate it on the stack2 astore_2 // Store duplicate in local variable 23 monitorenter // Enter the monitor associated with f4 aload_0 // Holding the monitor, pass this and...5 invokevirtual #5 // ...call Example.doSomething()V8 aload_2 // Push local variable 2 (f)9 monitorexit // Exit the monitor associated with f10 goto 18 // Complete the method normally13 astore_3 // In case of any throw, end up here14 aload_2 // Push local variable 2 (f)15 monitorexit // Be sure to exit the monitor!16 aload_3 // Push thrown exception...17 athrow // ...then rethrow the value to the invoker18 return // Return in the normal caseException table:From To Target Type
4 10 13 any
13 16 13 any
编译器必须确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter指令都必须有执行其对应 monitorexit指令,而无论这个方法是正常结束,还是异常结束。为了保证在方法异常完成时monitorenter 和 monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit 指令。
- JVM规范研读-2
- JVM规范研读-1
- JVM规范研读-3 Class文件格式
- JVM规范
- 精通java之JVM详解(每日研读2次以上,对java理解必有提升)
- jieba 分词源代码研读(2)
- springside4项目源码研读(2)
- tf API 研读2:math
- 深入理解JVM规范
- JVM规范学习:invokevirtual
- JVM规范学习:invokestatic
- JVM规范学习:invokespecial
- JVM规范学习:invokeinterface
- JVM规范学习:invokedynamic
- 【JVM规范笔记一】JVM虚拟机结构
- ApiDemos 2.3研读笔记(2)
- SUN JVM规范笔记1
- JVM规范在提及NoClassDefFoundError
- c++多线程编程遇到的问题小结
- 声讯电话加盟 吸费电话招商 2014创业项目
- 面试题1
- 电话群呼吸费 真的吸费电话 致富好平台
- 加值吸费电话 手机吸费代理 灰色产业暴利
- JVM规范研读-2
- 黑马程序员,黑马论坛-----黑马.Net6期,毕业118工作日,100%全部就业,平均薪水6062元!
- 响一声电话吸费 手机回拨吸费 无需经验
- 黑马程序员—[Android就业薪资] Android31期,毕业18个工作日,就业率71.95%,薪资9946元
- Buy the Ticket
- 【杂题】 HDOJ 4972 A simple dynamic programming problem
- netlink rtmsg 头文件
- 垂直同步图解
- 黑马程序员—[Android就业薪资] Android32期,毕业5个工作日,就业率48.65%,平均薪水9905元!