If a mutable link has changed between the thread loading the link and calling a function on it, could the old object be garbage collected?

I have two threads executing the following code:

static volatile Something foo;

void update() {
    newFoo = new Something();
    foo = newFoo;
}

void invoke() {
    foo.Bar();
}

      

Thread A executes update

and Thread B executes invoke

. The two threads have a tactic that invoke

loads the address foo

, update

overwrites foo

, and then garbage collection takes place before being called Bar

.

Is it possible that garbage collection can collect the old referenced object foo

, causing it to Bar

be called on some memory that was collected?

Please note that this question is mostly out of curiosity. I am also open to a better name.

+3


source to share


5 answers


The garbage collector suspends the state of all running threads long enough to resolve any race conditions associated with any available memory accesses. Regardless of whether a static variable is foo

volatile or not, the garbage collector will know the identifiers of any object to which a call might be called Bar

, and will ensure that any such object of an object will exist as long as there is any path of execution, via which can be accessed by any normal or "hidden" fields through which a call KeepAlive

to it can be made or through which it can refer - compared to another link.



It is possible that in some cases the system can call Finalize

to an object, while there are observable references to it, but the system maintains as an absolute invariant that the GC knows about all references that can be used in any way described above; objects are guaranteed to exist as long as such references exist.

+1


source


There are two things about this:



  • When the GC does a collection, it starts with so-called "roots" to determine if an object instance is relevant. The root can be a local variable stored on the stack, or even a register containing a reference to an object. When your code calls Bar (), the code is probably loading the address of the instance into a register (this was ECX years ago, I'm not sure now). It will be passed to the method as "this". If garbage collection occurs, ECX will be considered root and the instance will not be marked as garbage.
  • GC does not happen at random times. Before GC happens, threads will stop at designated "safe points", so the program will be in a good and consistent state for garbage collection. This helps avoid the situations you describe.
+1


source


There is a timing in two threads so that invoke loads the address of foo,

This alone gives you the answer. When the old value foo

is on the stack (in preparation for a call to .Bar ()) it considered the root reference. It will become (already is) a link this

inside the bar, and the instance can be built as soon as it is no longer needed. This can be when Bar () is executed.

Memory safety is never compromised here.

+1


source


There is a tactical timing in the two threads that invoke loads the address of foo, the update overwrites foo.

Since your static field is marked as volatile

, it is guaranteed at runtime that any change to the given field will be updated immediately and any thread consuming it will have the most recent updated value. Hence, the scenario is currently not possible.

If the field was not present volatile

, it foo

will contain a reference to the previous value, which makes it impossible to collect the GC, so it Bar()

will be called on the old reference, and not on "some memory that can be collected". Memory management in .NET is a deal with situations like this, so there will be no execution of any arbitrary unsafe memory address.

0


source


No, it will not be called into "collected memory". The method Bar()

is called either on the old object or on the newly created one. It depends on which one was first loaded onto the stack. (I don't think the stack has garbage collected)

It's pretty clear from the decompiled code:

.method private hidebysig static 
    void update () cil managed 
{
    // Method begins at RVA 0x2170
    // Code size 16 (0x10)
    .maxstack 1
    .locals init (
        [0] class ConsoleApplication1.Something newFoo
    )

    IL_0000: nop
    IL_0001: newobj instance void ConsoleApplication1.Something::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: volatile.
    IL_000a: stsfld class ConsoleApplication1.Something modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)  ConsoleApplication1.Program::foo
    IL_000f: ret
} // end of method Program::update


.method private hidebysig static 
    void invoke () cil managed 
{
    // Method begins at RVA 0x218c
    // Code size 15 (0xf)
    .maxstack 8

    IL_0000: nop
    IL_0001: volatile.
    IL_0003: ldsfld class ConsoleApplication1.Something modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)  ConsoleApplication1.Program::foo
    IL_0008: callvirt instance class ConsoleApplication1.Something ConsoleApplication1.Something::Bar()
    IL_000d: pop
    IL_000e: ret
} // end of method Program::invoke

      

stsfld

- Replaces the value of the static field with the value from the evaluation stack.

ldsfld

- pushes the static field value onto the evaluation stack.

callvirt

- calls the late binding method on the object, pushing the return value onto the evaluation stack.

0


source







All Articles