[Mono-dev] Why does .NET object lifetime not extend into an instance method call?
jonpryor at vt.edu
Fri Aug 24 17:50:25 UTC 2012
On Aug 24, 2012, at 1:11 PM, David Jeske <davidj at gmail.com> wrote:
> (1) Why would a call to an instance method not hold "this" alive for the entire duration of the call?
`this` isn't special, it's just an implicit variable passed into the method. If the variable isn't used within the method call, then it's collectible.
Rephrased, consider this:
Foo (new StringBuilder ());
static void Foo(StringBuilder b)
The variable `sb` isn't used at all within Foo(). Consequently, the StringBuilder instance can be collected at any time, and no one will notice (as far as the GC is concerned). (The StringBuilder allocation could be omitted entirely, actually, if the runtime environment were smart enough to determine that it wasn't doing anything...)
Since `this` is "just" a variable, the GC treats it in the same way. The issue isn't so much that the GC is treating P/Invoke specially; the issue is that it's not treating it specially at all, and P/Invoke introduces a "different world" (native code) which the GC doesn't know anything about. Consequently, the GC can (and will) collect instances that the GC knows are unreachable from managed code, but may still be referenced from native code.
> It seems this could happen in more cases than just PInvoke. This seems to allow a finalizer to run before an object is "done being used" anytime the object instance is not stored. (i.e. inside a statement of the form "new Foo().Method();") If the finalizer triggers an IDispose pattern, this could cause a managed resource to be torn down before it's done being used as well.
The managed resource can't be disposed before it's done being used AS LONG AS the GC knows about all uses of the managed resource.
In your `new Foo().Method()` example, it IS possible that the GC will finalize the `Foo` instance before Method() has returned, but it will only do so AS LONG AS `this` is no longer referenced within Method(). Thus, if Method() were empty or didn't use any instance members at all (e.g. the above Foo() body), then the instance can be collected while Method() is executing. Furthermore, it won't matter, as there's no way for Method() to even know that's happening.
The real problem is that the GC doesn't know anything about native code, and thus can't ensure that no native code is using the resource.
> Why isn't this considered a bug in the .NET runtime?
How would you fix it? The .NET runtime has no way of knowing what native code is doing, so short of disassembling the native code ("magic"), what is .NET supposed to do?
> (2) Does the Mono GC have the same behavior?
Yes, because there's no other sane behavior.
With Boehm it may be less of an issue, as Boehm is non-moving collector (so the memory won't be invalidated as quickly), and due to Boehm and Sgen's conservative stack walking nature Mono is more likely to preserve managed code which is referenced by native stack frames.
However, this can't be relied upon; Linux supports "precise stack marking," which prevents conservative scanning of native stack frames. This has the wonderful performance advantage that less memory needs to be pinned, allowing the GC to be more efficient:
More information about the Mono-devel-list