Java 的虚拟机与 CLR

作为对 MSIL 和 Java 字节码的区别?问题的一种跟进,Java 虚拟机的工作方式与 .NET 框架通用语言运行库(CLR)的工作方式有什么(主要)差异或相似之处?

此外,.NET 框架 CLR 是“虚拟机”还是没有虚拟机的属性?

45694 次浏览

CLR 和 JVM 都是虚拟机。

那个。NET 框架和 JRE 是相应虚拟机及其库的捆绑。没有库,虚拟机就毫无用处。

它不是一个虚拟机。Net 框架在第一次运行时将程序集编译成本机二进制文件:

在计算机领域,即时编译(JIT) ,也被称为动态翻译,是一种提高计算机程序运行时性能的技术。JIT 建立在运行时环境中早期的两个思想之上: 字节码编译和动态编译。它先在运行时将代码转换为本机代码,然后再执行本机代码,例如字节码转换为本机代码。与解释器相比,性能的提高源于对代码块翻译结果的缓存,而不是简单地在每次遇到代码块时重新评估每一行或每个操作数(见直译语言)。与在开发时静态编译代码相比,它还具有优势,因为如果发现这样做是有利的,它可以重新编译代码,并且可以强制执行安全保证。因此,JIT 可以结合解释和静态(提前)编译的一些优点。

一些现代运行时环境,比如微软的。NET Framework、大多数 Java 实现以及最近的 Actionscript 3都依赖于 JIT 编译来实现高速代码执行。

资料来源: http://en.wikipedia.org/wiki/Just-in-time_compilation

添加.NET 框架包含一个虚拟机,就像 Java 一样。

您的第一个问题是将 JVM 与。NET 框架-我假设你实际上是想与 CLR 进行比较。如果是这样的话,我认为你可以就此写一本小书(编辑:看起来像 Benji 已经有了: -)

一个重要的区别是,CLR 被设计成与语言无关的体系结构,与 JVM 不同。

另一个重要的区别是,CLR 是专门设计来允许与本机代码进行高水平的互操作的。这意味着,当访问和修改本机内存时,CLR 必须管理可靠性和安全性,还必须管理基于 CLR 的数据结构和本机数据结构之间的 管理编组

回答你的第二个问题,“虚拟机”这个术语来自硬件世界(例如 IBM 在20世纪60年代对360的虚拟化) ,过去指的是一个软件/底层机器的硬件模拟,可以完成与 VMware 相同的工作。

CLR 通常被称为“执行引擎”。在这个上下文中,这是一个基于 x86的 IL 机器的实现。这也是 JVM 所做的,尽管您可以认为 CLR 的多态字节码和 JVM 的类型化字节码之间有一个重要的区别。

所以你第二个问题的迂腐答案是“不”。但这实际上取决于你如何定义这两个术语。

编辑: JVM 和 CLR 之间的另一个区别是,JVM (版本6)是 非常不情愿,用于将分配的内存释放回操作系统,即使在它可以释放的地方。

例如,假设一个 JVM 进程最初从操作系统启动并分配25MB 的内存。然后,应用程序代码尝试分配额外需要50MB 的内存。JVM 将从操作系统中额外分配50MB。一旦应用程序代码停止使用该内存,它就会被垃圾收集,JVM 堆大小也会减小。但是,JVM 将只释放某些 非常特殊的情况下分配的操作系统内存。否则,在进程生命周期的剩余时间内,该内存将保持分配状态。

另一方面,如果不再需要已分配的内存,则 CLR 将其释放回操作系统。在上面的示例中,一旦堆减少,CLR 就会释放内存。

两种实现之间有很多相似之处(在我看来: 是的,它们都是“虚拟机”)。

首先,它们都是基于堆栈的 VM,没有我们在现代 CPU (如 x86或 PowerPC)中常见的“寄存器”概念。对所有表达式((1 + 1)/2)的求值是通过将操作数推到“堆栈”上,然后在指令(添加、除法等)需要使用这些操作数时将这些操作数从堆栈中弹出来来执行的。每条指令都将其结果推回到堆栈上。

这是实现虚拟机的一种方便的方法,因为世界上几乎每个 CPU 都有一个堆栈,但寄存器的数量通常是不同的(有些寄存器是特殊用途的,每条指令需要在不同的寄存器中使用其操作数,等等)。

所以,如果你要建模一个抽象机器,一个纯粹的基于堆栈的模型是一个很好的方法。

当然,真正的机器不是这样运作的。因此,JIT 编译器负责执行字节码操作的“注册”,实际上是调度实际的 CPU 寄存器,以便在可能的情况下包含操作数和结果。

因此,我认为这是 CLR 和 JVM 之间最大的共同点之一。

至于分歧..。

这两种实现之间一个有趣的区别在于,CLR 包含了创建泛型类型的指令,然后还包含了对这些类型应用参数化的指令。因此,在运行时,CLR 将 List < int > 视为与 List < String > 完全不同的类型。

在底层,它对所有引用类型专门化使用相同的 MSIL (因此 List < String > 使用与 List < Object > 相同的实现,在 API 边界使用不同的类型转换) ,但是每个值类型使用自己独特的实现(List < int > 生成与 List < double > 完全不同的代码)。

在 Java 中,泛型类型是纯粹的编译器技巧。JVM 不知道哪些类具有类型参数,并且无法在运行时执行参数化。

从实际的角度来看,这意味着不能在泛型类型上重载 Java 方法。不能有两个名称相同的不同方法,只能根据它们是接受 List < String > 还是 List < Date > 而有所不同。当然,由于 CLR 了解参数类型,所以在处理泛型类型专门化上重载的方法时没有问题。

在日常基础上,这就是我注意到的 CLR 和 JVM.

其他重要区别包括:

  • CLR 有闭包(实现为 C # 委托)。

  • CLR 具有协同程序(使用 C # ‘屈服’关键字实现) ,而 JVM 没有。

  • CLR 允许用户代码定义新的值类型(structs) ,而 JVM 提供一个固定的值类型集合(byte、 short、 int、 long、 float、 double、 char、 boolean) ,并且只允许用户定义新的引用类型(class)。

  • CLR 提供对声明和操作指针的支持。这一点特别有趣,因为 JVM 和 CLR 都采用严格的分代压缩垃圾收集器实现作为内存管理策略。在通常情况下,严格压缩的 GC 在处理指针时会非常困难,因为当您将一个值从一个内存位置移动到另一个位置时,所有的指针(以及指向指针的指针)都会变得无效。但是 CLR 提供了一种“钉住”机制,这样开发人员就可以声明一个代码块,在这个代码块中 CLR 不允许移动某些指针。非常方便。

  • JVM 中最大的代码单元要么是一个“包”(被“ protected”关键字证明) ,要么是一个 JAR (即 Java 存档) ,可以通过在类路径中指定一个 JAR 并将其视为代码文件夹来证明。在 CLR 中,类被聚合为“程序集”,CLR 提供逻辑来推理和操作程序集(这些程序集被加载到“ AppDomains”中,为内存分配和代码执行提供子应用程序级别的沙箱)。

  • CLR 字节码格式(由 MSIL 指令和元数据组成)比 JVM 具有更少的指令类型。在 JVM 中,每个惟一操作(添加两个 int 值、添加两个 float 值等)都有自己的唯一指令。在 CLR 中,所有 MSIL 指令都是多态的(添加两个值) ,JIT 编译器负责确定操作数的类型并创建适当的机器代码。但我不知道哪种策略更好。两者都有取舍。用于 JVM 的 HotSpot JIT 编译器可以使用更简单的代码生成机制(它不需要确定操作数类型,因为它们已经在指令中编码了) ,但这意味着它需要更复杂的字节码格式,以及更多的指令类型。

到现在为止,我已经使用 Java (并且很欣赏 JVM)大约十年了。

但是,在我看来,CLR 现在在几乎所有方面都是优越的实现。

关于差异的更多细节可以从各种学术和私人来源中找到。一个好的例子是 CLR 的设计选择

一些具体例子包括:

  • 一些低级操作数类型为“ add two int”,其中 CLR 使用多态操作数。(例如 fadd/iadd/ladd vs just add)
  • 目前,JVM 执行更积极的运行时分析和优化(即 Hotspot)。CLR 当前执行 JIT 优化,但不执行运行时优化(即在运行时替换代码)。
  • CLR 不内联虚方法,而 JVM..。
  • 除了“原语”之外,还支持 CLR 中的值类型。