145

作为对名为“MSIL 和 Java 字节码之间的差异”的问题的一种跟进?,Java虚拟机的工作方式与Java虚拟机的工作方式有什么(主要)差异或相似之处.NET 框架公共语言运行时 (CLR) 有效吗?

另外,是.NET 框架CLR 是“虚拟机”还是不具备虚拟机的属性?

4

5 回答 5

285

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

一方面,它们都是基于堆栈的虚拟机,没有我们习惯于在 x86 或 PowerPC 等现代 CPU 中看到的“寄存器”概念。所有表达式 ((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# 委托实现)。JVM 仅在 Java 8 之后才支持闭包。

  • CLR 具有协程(使用 C# 'yield' 关键字实现)。JVM 没有。

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

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

  • JVM 中最大的代码单元是“包”,如“受保护”关键字所证明的,或者可以说是 JAR(即 Java ARchive),如能够在类路径中指定 jar 并将其视为文件夹所证明的那样的代码。在 CLR 中,类被聚合成“程序集”,CLR 为推理和操作程序集(加载到“AppDomains”中,为内存分配和代码执行提供子应用程序级沙箱)提供逻辑。

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

我已经使用 Java(并且欣赏 JVM)大约十年了。

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

于 2009-01-26T16:21:50.357 回答
25

您的第一个问题是将 JVM 与 .NET Framework 进行比较——我假设您实际上是想与 CLR 进行比较。如果是这样,我想你可以写一本关于这个的小书(编辑:看起来 Benji 已经有了:-)

一个重要的区别是 CLR 被设计为一种语言中立的体系结构,这与 JVM 不同。

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

为了回答您的第二个问题,“虚拟机”一词是硬件世界的一个较旧的术语(例如 IBM 在 1960 年代对 360 的虚拟化),它曾经表示底层机器的软件/硬件模拟以完成相同类型的VMWare 所做的事情。

CLR 通常被称为“执行引擎”。在这种情况下,这是在 x86 之上的 IL 机器的实现。这也是 JVM 所做的,尽管您可以争辩说 CLR 的多态字节码和 JVM 的类型化字节码之间存在重要区别。

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

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

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

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

于 2009-01-17T17:54:54.027 回答
11

CLR 和 JVM 都是虚拟机。

.NET Framework 和 Java 运行时环境是各自 VM 及其库的捆绑。没有库,虚拟机就毫无用处。

于 2009-01-17T17:10:15.367 回答
11

可以从各种学术和私人来源中找到有关差异的更多细节。一个很好的例子是CLR Design Choices

一些具体的例子包括:

  • 一些低级操作数是键入的,例如“添加两个整数”,其中 CLR 使用多态操作数。(即 fadd/iadd/ladd 与只是添加)
  • 目前,JVM 进行更积极的运行时分析和优化(即 Hotspot)。CLR 当前进行 JIT 优化,但不进行运行时优化(即在运行时替换代码)。
  • CLR 不内联虚拟方法,JVM 确实...
  • 除了“原语”之外,还支持 CLR 中的值类型。
于 2009-01-27T23:06:44.703 回答
-11

它不是虚拟机,.net 框架在第一次运行时将程序集编译为本机二进制文件:

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

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

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

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

于 2009-01-17T17:24:01.703 回答