您可以使用此图表来理解字节码验证,详细说明在
Oracle文档中。您将发现字节码验证仅发生一次,而不是两次。
该示例显示了从Java语言源代码通过Java编译器、类加载器和字节码验证器到包含解释器和运行时系统的Java虚拟机的数据和控制流程。重要的问题是Java类加载器和字节码验证器对字节码流的主要来源不做任何假设——代码可能来自本地系统,也可能已经跨越半个地球。字节码验证器充当了一个门卫的角色:它确保传递给Java解释器的代码处于适合执行并且可以安全运行的状态,以避免破坏Java解释器。导入的代码在经过验证器的测试之前不能以任何方式执行。一旦验证器完成,就知道了一些重要的属性:
- 操作数堆栈没有溢出或下溢;
- 所有字节码指令的参数类型始终是正确的;
- 对象字段访问是合法的——私有的、公共的或受保护的。
虽然所有这些检查看起来都非常详细,但是一旦字节码验证器完成其工作,Java解释器就可以继续运行,因为它知道代码将安全地运行。了解这些属性使Java解释器更快,因为它不需要检查任何内容。没有操作数类型检查和堆栈溢出检查。解释器因此可以在不影响可靠性的情况下以全速运行。
从Oracle文档
第5.3.2节:
当类加载器L的loadClass方法使用要加载的类或接口C的名称N被调用时,L必须执行以下两个操作之一以加载C:
- 类加载器L可以创建一个表示C的字节数组,作为ClassFile结构的字节(§4.1);然后它必须调用ClassLoader类的defineClass方法。调用defineClass方法将使Java虚拟机使用§5.3.5中找到的算法从字节数组中派生由L命名的类或接口。
- 类加载器L可以将C的加载委托给其他类加载器L'。这是通过将参数N直接或间接传递到L'上的某个方法(通常是loadClass方法)的调用来实现的。调用的结果是C。
正如Holger所评论的那样,尝试通过
示例来更详细地解释。
static int factorial(int n)
{
int res;
for (res = 1; n > 0; n--) res = res * n;
return res;
}
相应的字节码将是
method static int factorial(int), 2 registers, 2 stack slots
0: iconst_1 // push the integer constant 1
1: istore_1 // store it in register 1 (the res variable)
2: iload_0 // push register 0 (the n parameter)
3: ifle 14 // if negative or null, go to PC 14
6: iload_1 // push register 1 (res)
7: iload_0 // push register 0 (n)
8: imul // multiply the two integers at top of stack
9: istore_1 // pop result and store it in register 1
10: iinc 0, -1 // decrement register 0 (n) by 1
11: goto 2 // go to PC 2
14: iload_1 // load register 1 (res)
15: ireturn // return its value to caller
请注意,JVM中的大部分指令都是有类型的。
现在您应该注意,除非代码至少满足以下条件,否则不能保证JVM的正常操作:
- 类型正确性:指令的参数始终是指令所期望的类型。
- 无堆栈溢出或下溢:指令从不从空堆栈弹出参数,也不会将结果推送到满堆栈上(其大小等于为方法声明的最大堆栈大小)。
- 代码包含性:程序计数器必须始终指向方法的代码内部,指向有效指令编码的开始位置(不要掉落方法代码的末尾;不要将分支转向指令编码的中间)。
- 寄存器初始化:从寄存器加载必须始终遵循对此寄存器的至少一次存储;换句话说,不对应于方法参数的寄存器在方法进入时不初始化,并从未初始化的寄存器加载是错误的。
- 对象初始化:创建类C的实例时,必须在使用类实例之前调用类C的初始化方法之一(与此类的构造函数相对应)。
字节码验证的目的是通过在加载时对字节码进行静态分析来一次性检查这些条件。通过验证的字节码可以更快地执行。
还要注意,字节码验证的目的是将上述验证从运行时移到加载时。
以上解释摘自Java bytecode verification: algorithms and formalizations
ClassLoader
内部发生,因此完全独立于特定的ClassLoader
实现。甚至还有其他方法可以将一个类添加到JVM中,例如Instrumentation,但在这些情况下也会验证字节码。同时,“Class Loader”指向“Just in Time Compiler”的箭头没有意义,因为ClassLoader不以任何方式与JIT Compiler交互。相反,您可以认为验证器和JIT已经成为JVM的一个组成部分超过15年了。 - HolgerClassLoader
负责定位和加载(或生成)组成类文件的字节。当它将这些字节传递给其中一个defineClass
方法时,它的责任就结束了。这是 JVM 及其验证器的责任开始的地方。该过程在 JVM 规范 §5.3 中有详细说明。请注意,5.3.2 包含有关 Java1.1 更改(1997 年)的备注。 - Holger