One of the subtle differences between the JVM bytecode instruction set and the CLR
instructin set is that the JVM splits object instantiation in two steps: 1) allocation
and 2) initialization. The CLR combines both steps in a single instruction.
This usually isn't a big difference. The CLR way makes the verifier a little
easier because it doesn't need to keep track of uninitialized references. However,
in the face of finalization, the difference is actually detectable.
Here is a Java example:
class foo {
static int throwException() {
throw new Error();
}
foo(int i) {
}
public static void main(String[] args) {
try {
new foo(throwException());
} catch(Error x) {
System.gc();
System.runFinalization();
throw x;
}
}
protected void finalize() {
System.out.println("finalize");
}
}
When this class is run, it prints "finalizable" and a stack trace. Looking at the
bytecode of the main method, it is obvious why:
0 new foo
3 invokestatic <Method foo throwException()I>
6 invokespecial <Method foo <init>(I)V>
9 goto 21
12 astore_1
13 invokestatic <Method java/lang/System gc()V>
16 invokestatic <Method java/lang/System runFinalization()V>
19 aload_1
20 athrow
21 return
The first instruction is a new that allocates the foo instance. Even if the
following call to throwException throws an exception, the foo instance already exists
and will be finalized.
Here is the (approximately) corresponding C# code:
class Foo {
Foo(int i) {
}
~Foo() {
System.Console.WriteLine("Finalize");
}
static int ThrowException() {
throw new System.Exception();
}
static void Main() {
try {
new Foo(ThrowException());
} catch(System.Exception x) {
System.Console.WriteLine(x);
}
}
}
When this code is run, the output should be just the stack trace, but it
turns out that it will also print "Finalize" (on .NET, not on Mono). This is a bug
in the .NET JIT. It moves the object allocation up to be able to more efficiently
construct the call stack for the constructor invocation, but in doing so it subtly
changes the semantics. When the above code is compiled with debugging enabled, it
works as expected.