ClassFile 对方法的约束
从 ClassFile 的角度来说,它对于方法接收的参数数量、方法体的大小做了约束。
方法参数的数量(255)
在一个方法当中,方法接收的参数最多有 255 个。我们分成三种情况来进行说明:
- 第一种情况,对于 non-static 方法来说,
this
也要占用 1 个参数位置,因此接收的参数最多有 254 个参数。 - 第二种情况,对于 static 方法来说,不需要存储
this
变量,因此接收的参数最多有 255 个参数。 - 第三种情况,不管是 non-static 方法,还是 static 方法,
long
类型或double
类型占据 2 个参数位置,所以实际的参数数量要小于 255。
public class HelloWorld {
public void test(int a, int b) {
// do nothing
}
public static void main(String[] args) {
for (int i = 1; i <= 255; i++) {
String item = String.format("int val%d,", i);
System.out.println(item);
}
}
}
- 问题:能否在文档中找到依据呢?
- 回答:能。
在Java Virtual Machine Specification的4.11. Limitations of the Java Virtual Machine部分对方法参数的数量进行了限制:
The number of method parameters is limited to 255 by the definition of a method descriptor, where the limit includes one unit for this
in the case of instance or interface method invocations.
方法体的大小(65535)
对于方法来说,方法体并不是想写多少代码就写多少代码,它的大小也有一个限制:方法体内最多包含 65535 个字节。
当方法体的代码超过 65535 字节(bytes)时,会出现编译错误:code too large。
- 问题:能否在文档中找到依据呢?
- 回答:能。
在Java Virtual Machine Specification的4.7.3. The Code Attribute部分定义了 Code
属性:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
code_length
: The value of thecode_length
item gives the number of bytes in thecode
array for this method. The value ofcode_length
must be greater than zero (as thecode
array must not be empty) and less than 65536.
Instruction VS. Opcode
严格的来说,instruction 和 opcode 这两个概念是有区别的。 相对来说,instruction 是一个比较大的概念,而 opcode 是一个比较小的概念:
instruction = opcode + operands
- 问题:能否在文档中找到依据呢?
- 回答:能。
A Java Virtual Machine instruction consists of a one-byte opcode specifying the operation to be performed, followed by zero or more operands supplying arguments or data that are used by the operation. Many instructions have no operands and consist only of an opcode. (本段内容来自于2.11. Instruction Set Summary的第一段)
粗略的来说,在现实生活当中谈论技术问题,我们经常混用 instruction 和 opcode 这两个概念,不作区分,可以将两者当成一回事儿。
Opcodes
一个字节的容量(256)
opcode 占用空间的大小为 1 byte。在 1 byte 中,包含 8 个 bit,因此 1 byte 最多可以表示 256 个值(即 0~255)。
opcode 的数量(205)
虽然一个字节(byte)当中可以存储 256 个值(即 0~255),但是在 Java 8 这个版本中定义的 opcode 数量只有 205 个。 因此,opcode 的内容就是一个“数值”,位于 0~255 之间。
opcode 和 mnemonic symbol
要记住每个 opcode 对应数值的含义,是非常困难的。 为了方便人们记忆 opcode 的作用,就给每个 opcode 起了一个名字,叫作 mnemonic symbol(助记符号)。
opcode | mnemonic symbol | opcode | mnemonic symbol | opcode | mnemonic symbol | opcode | mnemonic symbol |
---|---|---|---|---|---|---|---|
0 | nop | 64 | lstore_1 | 128 | ior | 192 | checkcast |
1 | aconst_null | 65 | lstore_2 | 129 | lor | 193 | instanceof |
2 | iconst_m1 | 66 | lstore_3 | 130 | ixor | 194 | monitorenter |
3 | iconst_0 | 67 | fstore_0 | 131 | lxor | 195 | monitorexit |
4 | iconst_1 | 68 | fstore_1 | 132 | iinc | 196 | wide |
5 | iconst_2 | 69 | fstore_2 | 133 | i2l | 197 | multianewarray |
6 | iconst_3 | 70 | fstore_3 | 134 | i2f | 198 | ifnull |
7 | iconst_4 | 71 | dstore_0 | 135 | i2d | 199 | ifnonnull |
8 | iconst_5 | 72 | dstore_1 | 136 | l2i | 200 | goto_w |
9 | lconst_0 | 73 | dstore_2 | 137 | l2f | 201 | jsr_w |
10 | lconst_1 | 74 | dstore_3 | 138 | l2d | 202 | breakpoint |
11 | fconst_0 | 75 | astore_0 | 139 | f2i | 203 | |
12 | fconst_1 | 76 | astore_1 | 140 | f2l | 204 | |
13 | fconst_2 | 77 | astore_2 | 141 | f2d | 205 | |
14 | dconst_0 | 78 | astore_3 | 142 | d2i | 206 | |
15 | dconst_1 | 79 | iastore | 143 | d2l | 207 | |
16 | bipush | 80 | lastore | 144 | d2f | 208 | |
17 | sipush | 81 | fastore | 145 | i2b | 209 | |
18 | ldc | 82 | dastore | 146 | i2c | 210 | |
19 | ldc_w | 83 | aastore | 147 | i2s | 211 | |
20 | ldc2_w | 84 | bastore | 148 | lcmp | 212 | |
21 | iload | 85 | castore | 149 | fcmpl | 213 | |
22 | lload | 86 | sastore | 150 | fcmpg | 214 | |
23 | fload | 87 | pop | 151 | dcmpl | 215 | |
24 | dload | 88 | pop2 | 152 | dcmpg | 216 | |
25 | aload | 89 | dup | 153 | ifeq | 217 | |
26 | iload_0 | 90 | dup_x1 | 154 | ifne | 218 | |
27 | iload_1 | 91 | dup_x2 | 155 | iflt | 219 | |
28 | iload_2 | 92 | dup2 | 156 | ifge | 220 | |
29 | iload_3 | 93 | dup2_x1 | 157 | ifgt | 221 | |
30 | lload_0 | 94 | dup2_x2 | 158 | ifle | 222 | |
31 | lload_1 | 95 | swap | 159 | if_icmpeq | 223 | |
32 | lload_2 | 96 | iadd | 160 | if_icmpne | 224 | |
33 | lload_3 | 97 | ladd | 161 | if_icmplt | 225 | |
34 | fload_0 | 98 | fadd | 162 | if_icmpge | 226 | |
35 | fload_1 | 99 | dadd | 163 | if_icmpgt | 227 | |
36 | fload_2 | 100 | isub | 164 | if_icmple | 228 | |
37 | fload_3 | 101 | lsub | 165 | if_acmpeq | 229 | |
38 | dload_0 | 102 | fsub | 166 | if_acmpne | 230 | |
39 | dload_1 | 103 | dsub | 167 | goto | 231 | |
40 | dload_2 | 104 | imul | 168 | jsr | 232 | |
41 | dload_3 | 105 | lmul | 169 | ret | 233 | |
42 | aload_0 | 106 | fmul | 170 | tableswitch | 234 | |
43 | aload_1 | 107 | dmul | 171 | lookupswitch | 235 | |
44 | aload_2 | 108 | idiv | 172 | ireturn | 236 | |
45 | aload_3 | 109 | ldiv | 173 | lreturn | 237 | |
46 | iaload | 110 | fdiv | 174 | freturn | 238 | |
47 | laload | 111 | ddiv | 175 | dreturn | 239 | |
48 | faload | 112 | irem | 176 | areturn | 240 | |
49 | daload | 113 | lrem | 177 | return | 241 | |
50 | aaload | 114 | frem | 178 | getstatic | 242 | |
51 | baload | 115 | drem | 179 | putstatic | 243 | |
52 | caload | 116 | ineg | 180 | getfield | 244 | |
53 | saload | 117 | lneg | 181 | putfield | 245 | |
54 | istore | 118 | fneg | 182 | invokevirtual | 246 | |
55 | lstore | 119 | dneg | 183 | invokespecial | 247 | |
56 | fstore | 120 | ishl | 184 | invokestatic | 248 | |
57 | dstore | 121 | lshl | 185 | invokeinterface | 249 | |
58 | astore | 122 | ishr | 186 | invokedynamic | 250 | |
59 | istore_0 | 123 | lshr | 187 | new | 251 | |
60 | istore_1 | 124 | iushr | 188 | newarray | 252 | |
61 | istore_2 | 125 | lushr | 189 | anewarray | 253 | |
62 | istore_3 | 126 | iand | 190 | arraylength | 254 | impdep1 |
63 | lstore_0 | 127 | land | 191 | athrow | 255 | impdep2 |
在上面表格当中,大家如果对任何一个 opcode 或 mnemonic symbol 感兴趣, 都可以参阅Chapter 6. The Java Virtual Machine Instruction Set。
mnemonic symbol 和类型信息
对于大多数的 opcode,它的 mnemonic symbol 名字当中带有类型信息(type information)。 一般情况下,规律如下:
i
表示int
类型l
表示long
类型。s
表示short
类型b
表示byte
类型c
表示char
类型f
表示float
类型d
表示double
类型。a
表示reference
类型。a
可能是 address 的首字母,表示指向内存空间的一个地址信息。
有一些 opcode,它的 mnemonic symbol 名字不带有类型信息(type information),但是可以操作多种类型的数据,例如:
arraylength
,无论是int[]
类型的数组,还是String[]
类型的数组,获取数组的长度都是使用arraylength
。ldc
、ldc_w
和ldc2_w
表示load constant 的缩写,可以加载各种常量值。- 与 stack 相关的指令,可以操作多种数据类型。
pop
表示出栈操作,dup
相关的指令是 duplicate 单词的缩写,表示“复制”操作;swap
表示“交换”。
还有一些 opcode,它的 mnemonic symbol 名字不带有类型信息(type information),它也不处理任何类型的数据。例如:
-
goto
- 问题:能否在文档中找到依据呢?
- 回答:能。
For the majority of typed instructions,
the instruction type is represented explicitly in the opcode mnemonic by a letter:
i
for an int
operation, l
for long, s
for short
, b
for byte
, c
for char
,
f
for float
, d
for double
, and a
for reference
.
Some instructions for which the type is unambiguous do not have a type letter in their mnemonic.
For instance, arraylength
always operates on an object that is an array.
Some instructions, such as goto
, an unconditional control transfer, do not operate on typed operands.
(本段内容来自于2.11.1. Types and the Java Virtual Machine的第二段)
Opcode 学习方式
学习新事物,一般有两种学习体验:
- 第一种,经过短期刻苦努力,然后突然明白一个知识点,给人一种如获至宝的感觉。
- 第二种,经过长期坚持探索,大的知识点要学习,小的知识点也不舍弃,慢慢积累,最后达到水滴石穿的效果。
学习这些 opcode,也是一个缓慢的过程,就是平时一点一点的学习、慢慢积累的过程。
推荐的学习方式:
- 第一步,打好基础。例如,Stack Frame 的结构、进入方法时的 Stack Frame 的初始状态、long 和 double 类型的占用空间的大小等。
- 第二步,以“使用”为导向,有选择性的、跳跃式的学习。当用到这个 opcode 了,如果不了解它,那我们再去学习它;不要一味贪多,想全部学下来。
在后续内容当中,我们会将 205 个 opcode 分成不同的类别进行介绍,大家根据自己的兴趣有选择性的学习就可以了。
总结
本文内容总结如下:
- 第一点,在 ClassFile 结构中,对于方法接收的参数和方法体的大小是有数量限制的。
- 第二点,instruction = opcode + operands
- 第三点,opcode 占用 1 个 byte 大小,目前定义了 205 个;为了方便记忆,JVM 文档为 opcode 提供了名字(即 mnemonic symbol),大多数的名字中带有类型信息。
- 第四点,学习 opcode 是一个长期积累的过程,不能一蹴而就。所以,推荐大家依据感兴趣的内容,进行有选择性的学习。