58

I read a book which referred to the .net CLR as a virtual machine? Can anyone justify this? What is the reason we need the concept of virtual machines on some development platforms?

Isn't it possible to develop a native framework [one without virtual machine] that is fully object oriented and as powerful as .net?

The book which refers to CLR as virtual machine is "Professional .Net Framework 2.0".

4

7 回答 7

104

这里有很多误解。我想如果你真的想要的话,你可以把 .Net 想象成一个虚拟机,但是让我们看看 .Net 框架是如何真正处理你的代码的。典型场景如下所示

  1. 您使用 C#、VB.Net、F# 或其他兼容语言编写 .Net 程序。
  2. 该代码被编译成一种中间语言 (IL),它类似于 Java 的字节码,分发给最终用户机器。
  3. 最终用户在安装了正确版本的 .Net 的计算机上首次调用该程序
  4. 计算机看到这是一个 .Net 程序集而不是“原始”机器代码,并将其传递给 JIT 编译器
  5. JIT 编译器将 IL 编译为完全原生的机器代码
  6. 本机代码在此程序执行期间保存在内存中。
  7. 保存的本机代码被调用,IL 不再重要。

这里有几个重要的点,但最重要的是,从来没有任何代码被解释过。相反,您可以在第 5 步中看到它被编译为本机代码。这与将其加载到虚拟机中存在巨大差异,原因如下:

  1. 完全编译的代码由 cpu 直接执行,而不是由额外的软件抽象层解释或翻译,这应该更快。
  2. JIT 编译器可以利用特定于运行程序的单个机器的优化,而不是满足于最低公分母。
  3. 如果您愿意,您甚至可以预编译代码,实质上对用户完全隐藏第 5 步。

我想您可以将其称为虚拟机,因为 JITter 从开发人员那里抽象出真实机器的细节。就我个人而言,我认为这并不正确,因为对于许多人来说,虚拟机意味着运行时抽象,远离 .Net 程序不存在的本机代码。

关于整个过程的另一个关键点是,它真正与“虚拟机”环境区分开来,它只是典型的过程。如果您真的愿意,您可以在分发之前预编译一个 .Net 程序集并将本机代码直接部署给最终用户(提示:在程序的整个生命周期中,它的速度总体上会变慢,因为您失去了特定于机器的优化)。当然,您仍然需要安装 .Net 运行时,但此时它与任何其他运行时 API 并没有太大区别;它更像是一个集合 dll,带有一个可以链接的漂亮 API,就像 Microsoft 还附带 Visual Studio 的 VB 或 C 运行时一样。这种方式将 IL 排除在外,使得 VM 名称更难证明其合理性。(我说“有点”是因为 IL 仍然被部署并用于验证保存的代码,但它本身从未被执行过)。

另一个明显的问题是缺少 VM 进程。当您运行您的应用程序时,没有运行常见的“沙盒”进程。与 Java 相比,如果您在程序运行时打开任务管理器,您将看到一个专门用于 Java VM 的进程,而应用程序的实际进程是由 VM 创建的沙箱内的一个线程。在 .Net 中,您可以直接在 Windows 任务管理器中看到应用程序的进程。

总而言之:你可以说 IL + CLR + JIT 一起组成了一个虚拟机。我个人不这么认为,但如果你相信,我不会和你争论。我想说的是,当您告诉某人 .Net 在虚拟机中运行而没有进一步解释时,您与该人交流的想法是“在主机进程中解释字节码”。这是错误的。


更新这个答案现在有点老了,而且事情已经改变,使.Net 不再像虚拟机。在容器时代,冷启动时间可能更重要,我的理解是,.Net Core 的最新版本有更多工具可以使部署本机代码——并在每次启动时跳过 JIT 步骤——变得更加容易。

于 2009-10-26T14:19:01.660 回答
30

与 Java 虚拟机 (JVM) 类似,.net CLR 是一个字节码解释虚拟机。

JVM 解释包含 java 字节码的程序,.net CLR 解释包含微软所谓的“中间语言 (IL)”指令的程序。这些字节码之间存在差异,但虚拟机是相似的,并渴望提供相似的功能。

这两种虚拟机实现都能够将它们的输入字节码编译成它们正在运行的计算机的机器语言。这称为“即时编译 (JIT)”,生成的输出代码称为“JIT 代码”。由于 JIT 代码包含计算机 CPU 机器语言的指令序列,因此该代码有时被称为“本机”代码。

但是,JIT 代码在质量和数量上与本机代码不同,如下所述。出于这个原因,本文认为 JIT 代码只不过是运行特定字节码程序时虚拟机的本机实现

这两种虚拟机 (VM) 都希望提供的一项功能是以防止某些危险编程错误的形式提供的安全性。例如,这个网站论坛的标题 stackoverflow 的灵感来自于本机代码中可能存在的一种此类危险错误。

为了提供安全性和执行安全性,VM 在“虚拟机级别”实现类型安全。需要分配给 VM 内存来存储保存在该内存位置中的数据类型。例如,如果一个整数被压入堆栈,就不可能从堆栈中弹出一个双精度数。禁止 C 风格的“联合”。禁止指针和直接访问内存。

如果结果是诸如 EXE 文件之类的本机二进制文件,我们无法通过对开发人员实施面向对象的语言框架来获得相同的好处。在这种情况下,我们将无法区分使用框架生成的本机二进制文件和恶意用户使用框架以外的源生成的 EXE。

在 VM 的情况下,类型安全是在程序员被允许访问的“最低级别”强制执行的。(暂时忽略可以编写托管的本机代码。)因此,没有用户会遇到执行需要直接访问内存位置和指针的危险操作之一的应用程序。

在实践中,.net CLR 实现了一种编写本机代码的方法,可以由 .net“托管”代码调用。在这种情况下,本机代码作者有责任不犯任何指针和内存错误。

由于 JVM 和 .net CLR 都执行 JIT 编译,因此任何一个 VM 实际上都会从提供的字节码创建一个本机编译的二进制文件。这种“JIT 代码”的执行速度比 VM 的解释器执行速度更快,因为即使是由 JIT 生成的机器语言代码也包含 VM 需要执行的所有 VM 安全检查。因此,JIT 输出代码不如本机代码快,本机代码通常不包含大量运行时检查。然而,这种速度性能缺陷被换成了对可靠性(包括安全性)的改进;特别是,防止使用未初始化的存储,强制分配的类型安全,执行范围检查(因此防止基于堆栈和堆的缓冲区溢出),对象生命周期由垃圾收集管理,动态分配是类型安全的。

于 2009-10-14T05:25:59.077 回答
9

The "Virtual Machine" part refers to the fact that .NET code is compiled into EXE's and DLL's as "Intermediate" Assembly language (IL) to run on a virtual machine, as opposed to real CPU assembly language. Then, at runtime the ILM is converted into real CPU assembly for execution (referred to as Just-in-time, or JIT compiling).

Sure, you could write a .NET compiler so that it would be compiled into CPU assembly language instead of IL. However, this would not be portable to all CPUs - you'd have to to compile a different version for each OS/CPU pair. But by compiling into ILM, you let the "Virtual Machine" handle the CPU and OS specific stuff.

于 2009-10-14T05:20:14.673 回答
3

JVM 和 CLR 都没有做任何与其他语言的大多数“虚拟机”所做的实质性不同的事情。现代,它们都使用 JIT 将虚拟指令(p 代码、字节码、中间语言指令,随便你怎么称呼)转换为“本机 CPU 硬件”指令(“机器代码”。)

事实上,第一个这样做的“虚拟机”是 Smalltalk 虚拟机。该创新的作者 Peter Deutsch 将其称为“动态翻译”,而不是 Java 普及的术语“JIT”。如果 Smalltalk “运行时执行环境”将被称为“虚拟机”(它仍然是这样称呼的),那么任何和所有其他基本上做同样事情的“运行时系统”也可以称为“虚拟机”。 "

于 2014-07-03T22:28:54.063 回答
2

The advantage of the CLR is the freedom to write code in whatever programming language the developer chooses, since the code will be compiled down to CLR before being interpreted into native calls. The .NET framework uses this JIT compilation to treat everything uniformly and output programs which work for the platform being deployed on, which is absent from compiled languages.

于 2009-10-14T05:21:36.650 回答
2

我有点老派,所以我也将 CLR 称为虚拟机。我的理由是 CLR 从中间字节码组装机器码,这也是虚拟机所做的。

CLR 的好处主要在于它组装机器代码的方式,该机器代码利用了运行时类型信息。

您可以仅使用本机类型开发与 .NET 框架一样强大的本机框架。如果您将程序传输到另一个平台而无需重新编译,您唯一失去的灵活性就是重新组装本机代码的能力。

于 2009-10-14T05:23:32.523 回答
1

你有很多有价值的答案,但我认为还没有提到一件事:模块化。

从原生 DLL 导出 OO 类非常困难。当然,您可以告诉链接器导出该类并将其导入其他地方,但这很脆弱;更改类中的单个私有成员将破坏二进制兼容性,即如果您更改一个 DLL 而不重新编译所有其他模块,您的程序将在运行时严重崩溃。

有一些方法可以解决这个问题:例如,您可以定义公共抽象接口,从这些接口派生并从您的 DLL 导出全局工厂函数。这样,您可以更改类的实现细节。但是您不能从另一个 DLL 中的该类派生。当然,更改接口也会破坏二进制兼容性。

我不确定在本机代码中是否有一个好的解决方案:如果编译器/链接器在编译时创建本机代码,那么它必须知道代码中使用的类/结构的确切内存布局。如果最后一个编译步骤(生成本机代码)延迟到第一次调用某个方法,这个问题就迎刃而解了:您可以修改程序集中的类,只要 JIT 可以解析所有使用的成员运行时,一切都会运行良好。

简而言之:如果您创建一个单一的可执行程序,您可能会拥有 .NET 的大部分强大功能,并带有一个可以创建本机代码的编译器。但在大多数情况下,拥有 JIT 编译器的缺点(框架安装、启动时间稍长)并没有超过好处。

于 2009-10-26T15:06:38.117 回答