MSIL和Java字节码之间的区别是什么?

85

我是 .Net 的新手,希望先了解一些基础知识。 MSIL 和 Java 字节码有何区别?


1
相关问题:https://dev59.com/23RB5IYBdhLWcg3w9b19 - Frank V
2
一个稍微更一般的比较在这里: https://dev59.com/-nRB5IYBdhLWcg3wQFHu - A R
8个回答

82
首先,我要说的是,我认为Java字节码和MSIL之间的微小差别不应该困扰一个初学者的.NET开发人员。它们都具有定义抽象目标机器的相同目的,这是在最终使用的物理机器之上的一层。

MSIL和Java字节码非常相似,事实上,曾经有一个名为Grasshopper的工具,可以将MSIL转换为Java字节码。我曾经是Grasshopper开发团队的一员,因此我可以分享一些我(已经褪色的)知识。请注意,我停止在.NET Framework 2.0发布时工作,因此其中一些内容可能不再正确(如果是,请留下评论,我会进行更正)。

  • .NET允许用户定义的类型具有值语义,而不是“常规”的引用语义(struct)。
  • .NET支持无符号类型,这使得指令集更加丰富。
  • Java在字节码中包括方法的异常说明。虽然异常说明通常只由编译器强制执行,但如果使用默认之外的类加载器,则JVM可能会强制执行。
  • .NET泛型表达为IL,而Java泛型只使用type erasure
  • .NET属性在Java中没有等价物(这仍然正确吗?)。
  • .NET enums只是整数类型的包装器,而Java enums几乎是完整的类(感谢Internet Friend的评论)。
  • .NET具有outref参数。

还有其他语言差异,但大多数差异都未在字节码级别表示。例如,如果记忆无误,Java的非static内部类(在.NET中不存在)不是字节码特性,编译器会为内部类的构造函数生成一个额外的参数并传递外部对象。.NET lambda表达式也是如此。


1
关于属性 - Java注解可以设置为出现在字节码中,因此有一个等效的方式。 - Oak
2
@Oak:Java注解只允许传递数据,而.NET属性是完全支持类,可以具有逻辑,并且最重要的是可以实现接口。 - Fyodor Soikin
字节码还为每种返回类型提供了单独的返回指令,不知道它是否真正有助于类型安全。 - Cecil Dishwasher
2
.NET中值类型有时可能分配在堆栈上的事实,与它们具有值语义相比微不足道;每个值类型存储位置都是一个实例。相比之下,Java中的每个存储位置都是原始类型或混杂对象引用;没有其他类型。 - supercat
2
想知道它们在性能方面如何比较?例如,MSIL解释速度比字节码快吗? - Luke T O'Brien

26

CIL(MSIL的准确名称)和Java字节码之间的相似性大于它们的不同之处。然而,它们之间还是有一些重要的区别:

1) CIL 从一开始就被设计成为多种语言的目标平台。因此,它支持更丰富的类型系统,包括有符号和无符号类型、值类型、指针、属性、委托、事件、泛型、具有单个根的对象系统等等。CIL 还支持了最初的 CLR 语言(如 C# 和 VB.NET)不需要的特性,例如全局函数和 尾调用优化。相比之下,Java 字节码是为 Java 语言设计的目标平台,反映了 Java 本身的许多限制。使用 Java 字节码编写 C 或 Scheme 程序将会更加困难。

2)CIL 被设计为可以轻松集成到本地库和非托管代码中。

3)Java 字节码既可以解释执行也可以编译执行,而 CIL 只被设计为 JIT 编译执行。话虽如此,Mono 的最初实现使用了解释器而不是 JIT。

4)CIL 被设计(并规定)有一个人类可读和可写的汇编语言形式,直接映射到字节码形式。我相信 Java 字节码(如其名称所示)只被设计为机器可读。当然,Java 字节码相对容易反编译回原始的 Java,也可以像下面所示那样进行“反汇编”。

值得注意的是,JVM(大多数实现)比 CLR(任何实现)更高度优化。因此,原始性能可能是选择针对 Java 字节码的原因。不过这只是一个实现细节。

有些人说,Java 字节码是设计为多平台使用的,而 CIL 是专门为 Windows 设计的。但事实并非如此。.NET Framework 中有一些 "Windows" 主义,但在 CIL 中却没有。

作为上述第 4 点的例子,我以前写过一个玩具级的 Java 到 CIL 编译器。如果您向该编译器提供以下 Java 程序:

class Factorial{
    public static void main(String[] a){
    System.out.println(new Fac().ComputeFac(10));
    }
}

class Fac {
    public int ComputeFac(int num){
    int num_aux ;
    if (num < 1)
        num_aux = 1 ;
    else 
        num_aux = num * (this.ComputeFac(num-1)) ;
    return num_aux ;
    }
}

我的编译器将输出以下CIL代码:

.assembly extern mscorlib { }
.assembly 'Factorial' { .ver  0:0:0:0 }
.class private auto ansi beforefieldinit Factorial extends [mscorlib]System.Object
{
   .method public static default void main (string[] a) cil managed
   {
      .entrypoint
      .maxstack 16
      newobj instance void class Fac::'.ctor'()
      ldc.i4 3
      callvirt instance int32 class Fac::ComputeFac (int32)
      call void class [mscorlib]System.Console::WriteLine(int32)
      ret
   }
}

.class private Fac extends [mscorlib]System.Object
{
   .method public instance default void '.ctor' () cil managed
   {
      ldarg.0
      call instance void object::'.ctor'()
      ret
   }

   .method public int32 ComputeFac(int32 num) cil managed
   {
      .locals init ( int32 num_aux )
      ldarg num
      ldc.i4 1
      clt
      brfalse L1
      ldc.i4 1
      stloc num_aux
      br L2
   L1:
      ldarg num
      ldarg.0
      ldarg num
      ldc.i4 1
      sub
      callvirt instance int32 class Fac::ComputeFac (int32)
      mul
      stloc num_aux
   L2:
      ldloc num_aux
      ret
   }
}

这是一个有效的CIL程序,可以输入到类似ilasm.exe的CIL汇编器中以创建可执行文件。正如您所见,CIL是一种完全人类可读和可写的语言。您可以在任何文本编辑器中轻松地创建有效的CIL程序。

您还可以使用javac编译器编译上面的Java程序,然后通过javap“反汇编器”运行生成的类文件来获取以下内容:

class Factorial extends java.lang.Object{
Factorial();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   new #3; //class Fac
   6:   dup
   7:   invokespecial   #4; //Method Fac."<init>":()V
   10:  bipush  10
   12:  invokevirtual   #5; //Method Fac.ComputeFac:(I)I
   15:  invokevirtual   #6; //Method java/io/PrintStream.println:(I)V
   18:  return

}

class Fac extends java.lang.Object{
Fac();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public int ComputeFac(int);
  Code:
   0:   iload_1
   1:   iconst_1
   2:   if_icmpge   10
   5:   iconst_1
   6:   istore_2
   7:   goto    20
   10:  iload_1
   11:  aload_0
   12:  iload_1
   13:  iconst_1
   14:  isub
   15:  invokevirtual   #2; //Method ComputeFac:(I)I
   18:  imul
   19:  istore_2
   20:  iload_2
   21:  ireturn
}

javap 命令输出的内容(据我所知)无法编译,但是如果你将其与上面的 CIL 输出进行比较,你会发现它们非常相似。


2
原来已经有人尝试创建一种可读/可写的Java汇编语言。我发现的两个是JasminJava Bytecode Assembler - Justin
3
我已经写了一个更好的程序,不像Jasmin,它可以拆卸和重装任何有效的类文件。链接为https://github.com/Storyyeller/Krakatau。 我认为更准确地说,微软提供了标准的汇编器,而Java开发人员必须自己制作汇编器。 - Antimony

24

它们本质上是在做相同的事情,MSIL是微软版本的Java字节码。

内部主要的区别有:

  1. 字节码既用于编译也用于解释,而MSIL专门为JIT编译而开发
  2. MSIL被开发为支持多种语言(如C#和VB.NET等),而Bytecode仅针对Java编写,结果导致Bytecode在语法上更类似于Java,而IL则不像任何特定的.NET语言
  3. MSIL在值类型和引用类型之间有更明显的界限

更多信息和详细比较可以在 K John Gough 的文章 (后缀文档)中找到。


"1. Bytecode 旨在编译和解释,而 MSIL 则专为 JIT 编译而开发。" - 这段话描述了 Java 代码被编译为 bytecode 并且这个 bytecode 需要被解释执行的过程。我说得对吗?MSIL 不需要解释执行吗? - Honinbo Shusaku

2
并没有太大的区别。两者都是您编写的代码的中间格式。当执行时,虚拟机将执行中间语言管理,这意味着虚拟机控制变量和调用。甚至有一种语言,我现在记不起来了,可以在 .Net 和 Java 上以相同的方式运行。
基本上,它只是相同东西的另一种格式。
编辑:找到了这种语言(除了 Scala):它是 FAN(http://www.fandev.org/),看起来非常有趣,但还没有时间评估。

Scala可以编译为JVM或CLR的目标,分别生成字节码或MSIL。 - Daniel Spiewak
好知道,但我大约一个月前在阅读 DZone 时发现了另一种语言:找到它了!请参见我的帖子的编辑。 - GHad

2

CIL(通常称为MSIL)旨在是人可读的,而Java字节码则不是。

可以将Java字节码视为不存在的硬件的机器码(但JVM会模拟它)。

CIL更像汇编语言-距离机器码只有一步之遥,同时仍然可被人类读懂。


1
只要你有十六进制编辑器,字节码实际上非常易读。它是一种相当简单的基于堆栈的语言,具有直接表示类和方法的扩展功能。我认为MSIL更低级(例如寄存器)? - Daniel Spiewak
一个是“原始的”CIL。另一个是“反编译的”字节码。如果你了解十六进制,字节码可能相对容易读懂,但这不是设计目标。 - slim
“Disassembled”这个词用得不太准确,也许可以用“解码”代替。.class文件中的字节码之所以无法读取,仅仅是为了紧凑性。与javap的手册相反,在从编译后的类中生成可读的字节码时,并没有涉及到反汇编过程。 - Daniel Spiewak

2

1

我认为 MSIL 不应该与 Java 字节码进行比较,而是应该与“组成 Java 字节码的指令”进行比较。

没有反汇编后的 Java 字节码名称。“Java Bytecode”应该是一个非官方的别名,因为我在官方文档中找不到它的名称。 Java 类文件反汇编器

打印出反汇编代码,即类中每个方法所包含的 Java 字节码指令。这些指令在 Java 虚拟机规范中有记录。

“Java VM 指令”和“MSIL”都被组装成 .NET 字节码和 Java 代码,这些代码都无法被人类读取。


1

同意,对于初学者来说,这些差异足够微小,可以忽略不计。如果你想从基础开始学习 .Net,我建议先了解公共语言基础结构和公共类型系统。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接