字节码的验证是否发生两次?

时间:2014-08-28 06:04:44

标签: java jvm verification

所以我对JVM中发生的字节码验证有点困惑。根据 Deitel和Deitel 的书,Java程序经历了五个阶段(编辑,编译,加载,验证和执行)(第1章)。字节码验证器在“验证”阶段验证字节码。这本书没有提到字节码验证器是类加载器的一部分。

然而根据 docs of oracle ,类加载器执行加载,链接和初始化的任务,并且在链接过程中它必须验证字节码。

现在,是Deitel和Deitel谈论的字节码验证,以及字节码验证 this oracle document  谈到,同样的过程?

字节码验证是否会发生两次,一次是在链接过程中,另一次是字节码验证器?

描述了Dietel和Dietel在书中提到的java程序阶段的图片。(我从 nobalG :)的下面的答案中借用了这张图片) enter image description here

5 个答案:

答案 0 :(得分:21)

您可以使用此图表了解字节代码验证,详细说明在Oracle docs

enter image description here

你会发现字节码验证只发生一次而不是两次

  

该图显示了Java语言的数据流和控制流   源代码通过Java编译器,到类加载器和   字节码验证器,因此到Java虚拟机,其中   包含解释器和运行时系统。重要的问题是   Java类加载器和字节码验证器没有   关于字节码流的主要来源的假设 - 代码   可能来自当地系统,或者它可能已经中途旅行   在这个星球上。字节码验证器充当一种网守:   它确保传递给Java解释器的代码处于适合状态   要执行并且可以运行而不必担心破坏Java   翻译。不允许以任何方式执行导入的代码   直到它通过验证者的测试。一旦验证者是   完成后,众所周知一些重要的属性:

     
      
  • 没有操作数堆栈溢出或下溢
  •   
  • 已知所有字节码指令的参数类型始终正确
  •   
  • 已知对象字段访问是合法的 - 私有,公共或受保护的
  •   
     

到目前为止,所有这些检查都显得非常详细   字节码验证器完成了它的工作,Java解释器可以   知道代码将安全运行,继续。知道这些   属性使Java解释器更快,因为它没有   必须检查任何东西。没有操作数类型检查,也没有堆栈   溢出检查。因此,解释器可以全速运行   在不影响可靠性的情况下。

修改: -

来自Oracle Docs Section 5.3.2

  

使用时调用类加载器L的loadClass方法   要加载的类或接口C的名称N,L必须执行其中一个   以下两个操作是为了加载C:

     
      
  • 类加载器L可以创建一个字节数组,表示C作为ClassFile结构的字节(§4.1);然后它必须调用   类ClassLoader的方法defineClass。调用defineClass   导致Java虚拟机派生类或接口   使用该算法使用来自字节数组的L表示为N.   见§5.3.5。
  •   
  • 类加载器L可以将C的加载委托给其他类加载器L'。这是通过传递参数N来完成的   直接或间接地调用L'上的方法   (通常是loadClass方法)。调用的结果是   下进行。
  •   

Holger正确评论,试图在example的帮助下解释更多:

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

答案 1 :(得分:9)

没有。

来自JVM Spec 4.10

  

尽管Java编程语言的编译器必须只生成满足前面部分中所有静态和结构约束的类文件,但Java虚拟机无法保证任何要求加载的文件都是由该编译器生成的或者是否正确形成。

然后继续指定验证过程。

JVM Spec 5.4.1

  

验证(§4.10)确保类或接口的二进制表示在结构上是正确的(第4.9节)。验证可能会导致加载其他类和接口(第5.3节),但不需要对它们进行验证或准备。

指定链接引用§4.10的部分 - 不是作为单独的进程而是加载类的一部分。

当您遇到类似问题时,JVM和JLS是很棒的文档。

答案 2 :(得分:9)

没有这样的两次验证

,就验证而言,请仔细查看java中编写的程序如何经历下图中的各个阶段,您将看到没有这样两次验证,但代码只需验证一次。

enter image description here

  • 编辑 - 程序员编写程序(最好是在记事本上) 并将其保存为“.java”文件,然后进一步使用 汇编,由编译器编写。
  • COMPILE - 这里的编译器接受'.java'文件,编译它 并查找程序范围内的任何可能的错误。如果 它发现任何错误,它会向程序员报告。如果没有错误 在那里,然后程序转换为字节码和 保存为'.class'文件。

  • LOAD - 现在该组件的主要用途称为“类加载器” 是在JVM中加载字节代码。它还没有执行代码, 但只是将其加载到JVM的内存中。

  • VERIFY - 加载代码后,JVM的子部件名为'Byte 代码验证器'检查字节码并验证它的字节码 真实性。它还检查字节码是否具有任何此类代码 这可能会导致一些恶意结果。这个组成部分 JVM确保安全。

  • EXECUTE - 下一个组件是执行引擎。执行 引擎使用Just In Time(JIT)逐行解释代码 编译器。 JIT编译器执行起来非常快,但是 消耗额外的缓存。

答案 3 :(得分:5)

规范列出了字节码验证中的4个阶段。这些步骤在功能上是截然不同的,不要错过重复相同的事情。就像多次传递编译器使用每个传递来设置下一次传递一样,阶段不是重复,而是为了一个整体目的而编排,每个阶段完成某些任务。

除非更改字节码,否则没有理由对其进行两次验证。

此处描述了验证。

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10

答案 4 :(得分:2)

验证代码发生两次。在编译期间(如果代码存在缺陷,威胁则编译失败)并且在执行期间将类加载到内存中之后再次执行(此处发生实际的字节码验证)。是的,这会发生以及加载类(通过类加载器)的过程,但类加载器本身可能不会充当验证器。它是进行验证的JVM(或者更确切地说是JVM中的验证程序)。