字节码的验证会发生两次吗? [复制]
Posted
技术标签:
【中文标题】字节码的验证会发生两次吗? [复制]【英文标题】:Does verification of byte code happen twice? [duplicate] 【发布时间】:2014-10-21 21:37:56 【问题描述】:所以我对 JVM 内部发生的字节码验证有点困惑。根据Deitel 和Deitel 的书,Java 程序经历了五个阶段(编辑、编译、加载、验证和执行)(第 1 章)。字节码验证器在“验证”阶段验证字节码。书中没有提到字节码验证器是类加载器的一部分。
但是根据 docs of oracle ,类加载器执行加载、链接和初始化的任务,在链接过程中它必须验证字节码。
现在,是Deitel和Deitel谈论的字节码验证,以及 this oracle document 说说,同样的过程?
或者字节码验证会发生两次,一次在链接过程中,另一次由字节码验证器进行?
描述 Dietel 和 Dietel 书中提到的 Java 程序阶段的图片。(我从 nobalG 的以下答案之一借用了这张图片:)
【问题讨论】:
【参考方案1】:你可以用这个图来理解字节码验证,在Oracle docs中有详细的解释
你会发现字节码验证只发生一次而不是两次
插图显示了来自 Java 语言的数据流和控制 源代码通过Java编译器,到类加载器和 字节码验证器,因此进入 Java 虚拟机,它 包含解释器和运行时系统。重要的问题是 Java 类加载器和字节码验证器不会 关于字节码流的主要来源的假设——代码 可能来自本地系统,或者它可能已经中途旅行 环绕地球。字节码验证器充当一种看门人: 它确保传递给 Java 解释器的代码处于合适的状态 被执行并且可以运行而不用担心破坏Java 口译员。不允许以任何方式执行导入的代码 直到它通过了验证者的测试。一旦验证者 完成后,一些重要的属性是已知的:
没有操作数堆栈上溢或下溢 已知所有字节码指令的参数类型总是正确的 已知对象字段访问是合法的——私有、公共或受保护虽然所有这些检查看起来都非常详细,但到了 字节码验证器已经完成了它的工作,Java 解释器可以 继续,知道代码将安全运行。了解这些 properties 使 Java 解释器更快,因为它不 必须检查任何东西。没有操作数类型检查,也没有堆栈 溢出检查。因此,解释器可以全速运行 不影响可靠性。
编辑:-
来自 Oracle 文档Section 5.3.2:
当类加载器 L 的 loadClass 方法被调用时 要加载的类或接口 C 的名称 N,L 必须执行以下之一 以下两个操作以加载 C:
类加载器 L 可以创建一个字节数组,将 C 表示为 ClassFile 结构的字节(第 4.1 节);然后它必须调用 类 ClassLoader 的方法 defineClass。调用定义类 使 Java 虚拟机派生一个类或接口 使用算法从字节数组中使用 L 表示 N 见第 5.3.5 节。 类加载器 L 可以将 C 的加载委托给其他类加载器 L'。这是通过传递参数 N 直接或间接调用 L' 上的方法 (通常是 loadClass 方法)。调用的结果是 C.
正如 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
【讨论】:
一个快速的问题。哪个类加载器?。是否只有自定义类加载器接受此验证? @TheLostMind:- 我认为它并不特定于任何特定的类加载器,字节码验证适用于所有类文件。 @TheLostMind:这是一个简化的插图。实际上,验证不会发生在ClassLoader
中,因此完全独立于特定的ClassLoader
实现。甚至还有其他方法可以将类添加到 JVM,例如检测,但在这些情况下也会验证字节码。此外,从“类加载器”到“即时编译器”的箭头没有任何意义,因为 ClassLoader
不会以任何方式与 JIT 编译器交互。相反,您可以将验证程序和 JIT 视为 JVM 的组成部分已有 15 年多的时间了。
@Smrita:ClassLoader
负责定位和加载(或生成)构成类文件的字节。当它将这些字节传递给defineClass
方法之一时,它的职责就结束了。这就是 JVM 及其验证器的责任开始的地方。该过程在JVM spec §5.3 中指定。请注意,5.3.2 包含有关 Java1.1 更改 (1997) 的注释。
那一章是合适的资源。正如我在之前的评论中所说,第 5.3.2 节包含有关 Java 1.1 相关更改的评论。让我引用一下:“从 JDK 1.1 版开始,Oracle 的 Java 虚拟机实现直接链接类或接口,而不依赖于类加载器。”【参考方案2】:
没有。
来自JVM Spec 4.10:
尽管 Java 编程语言的编译器必须只生成满足前面部分中所有静态和结构约束的类文件,但 Java 虚拟机不能保证它被要求加载的任何文件都是由该编译器生成的或格式正确。
然后继续指定验证过程。
还有JVM Spec 5.4.1:
验证(§4.10)确保类或接口的二进制表示在结构上是正确的(§4.9)。验证可能会导致加载其他类和接口(第 5.3 节),但不需要验证或准备它们。
指定链接参考 §4.10 的部分 - 不是作为一个单独的过程,而是加载类的一部分。
当您有这样的问题时,JVM 和 JLS 是很好的文档。
【讨论】:
【参考方案3】:没有这样的二次验证
NO,就验证而言,仔细看下图中java写的程序是如何经历各个阶段的,你会发现没有这样两次验证,但代码只验证一次。
EDIT – 程序员编写程序(最好在记事本上) 并将其保存为“.java”文件,然后进一步用于 编译,由编译器。COMPILE – 此处的编译器采用“.java”文件,对其进行编译 并在程序范围内查找任何可能的错误。如果 它发现任何错误,将它们报告给程序员。如果没有错误 在那里,然后程序被转换成字节码和 保存为“.class”文件。
LOAD – 现在称为“类加载器”的组件的主要用途 就是在JVM中加载字节码。它还没有执行代码, 但只是将其加载到 JVM 的内存中。
VERIFY – 加载代码后,JVM 的子部分称为“字节” 代码验证器'检查字节码并验证它的 真实性。它还检查字节码是否有任何这样的代码 这可能会导致一些恶意的结果。该组件的 JVM 确保安全。
EXECUTE – 下一个组件是执行引擎。执行 引擎使用即时 (JIT) 逐行解释代码 编译器。 JIT 编译器的执行速度非常快,但是 消耗额外的缓存内存。
【讨论】:
这是Dietel和Dietel中已经提到的图表。它没有谈论字节码验证器是类加载器的一部分!即使这张图也不清楚。这张图是我困惑的主要原因! 也看到这个***.com/questions/755005/…【参考方案4】:规范列出了字节码验证的 4 个阶段。这些步骤在功能上是不同的,不要误认为是重复同样的事情。就像多通道编译器使用每个通道来设置下一个通道一样,阶段不是重复的,而是为单一的总体目的而编排的,每个阶段完成某些任务。
除非字节码改变,否则没有理由重复验证。
这里描述了验证。
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10
【讨论】:
【参考方案5】:代码的验证发生两次。一次是在编译期间(如果代码有缺陷、威胁,编译会失败),另一次是在执行期间类被加载到内存之后(实际的字节码验证发生在这里)。是的,这与(通过类加载器)加载类的过程一起发生,但类加载器本身可能不充当验证器。它是进行验证的 JVM(或者更确切地说是 JVM 中的验证器)。
【讨论】:
所以你的意思是编译器中有东西能够验证字节码?您能否指出资源,以便我也可以阅读:) @Smrita - 检查this 和this。顺便说一句,我编辑了我的答案以使其更清楚。 字节码验证不会发生两次。编译器确保 bad 代码总是失败。所以,这确实是验证,但不是字节码。 JVM 有一个执行字节码验证的验证器。 现在有点清楚了。所以似乎字节码的验证只发生一次:) @Smrita - 是的。好像是这样的。不幸的是,关于这个主题的可用文档要么过时,要么太少。以上是关于字节码的验证会发生两次吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章