cgarbage-collectionboehm-gc

How does Boehm's Garbage Collector free memory without creating a separate thread for the GC?


In C, the normal dynamic allocation workflow is like this:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = malloc(sizeof(int));

    if (ptr == NULL) 
        return 1;

    // Free the allocated memory
    free(ptr);

    return 0;
}

Any omission of free() causes a memory leak.

If we use Boehm's GC, the workflow is like this:

#include <stdio.h>
#include <gc.h>

int main() {
    int *ptr = GC_MALLOC(sizeof(int));

    // No need to free memory explicitly, the GC will handle it

    return 0;
}

This doesn't cause a memory leak as I know.

My question is: what exactly frees the allocated memory?

I mean, as I understand, the GC_MALLOC does a garbage collection cycle if needed, allocates the memory, and then returns (and that's the end of it), and the program continues. But I don’t see any subsequent calls to GC functions to free the memory. Does this mean that GC_MALLOC() creates a separate thread for the GC to run in, to be able to free the memory later?

In other words, how does the GC continue to work if all its functions have returned? Does it use atexit()?


Solution

  • In your first example, removing free does not leak memory, because your program immediately exits and the operating system reclaims any memory you did not explicitly free. All modern operating systems do this and have done this for decades, and Bohem's GC depends on this behavior.

    You can manually trigger a final GC pass if you want to, but you shouldn't. The operating system is better at reclaiming memory than the GC is.

    The FAQ explains this:

    Q: I want to ensure that all my objects are finalized and reclaimed before process exit. How can I do that?

    A: You can't, and you don't really want that. This would require finalizing reachable objects. Finalizers run later would have to be able to handle this, and would have to be able to run with randomly broken libraries, because the objects they rely on where previously finalized. In most environments, you would also be replacing the operating systems mechanism for very efficiently reclaiming process memory at process exit with a significantly slower mechanism.

    The FAQ also explains that, in your second example using GC_MALLOC, the GC never actually runs, and it's the OS that cleans up unfreed memory:

    Q: I wrote a test program which allocates objects and registers finalizers for them. Only a few (or no) objects are finalized. What's wrong?

    A: Probably nothing. Finalizers are only executed if all of the following happen before the process exits:

    • A garbage collection runs. This normally happens only after a significant amount of allocation.
    • The objects in question appear inaccessible at the time of the collection. It is common for a handful of objects to appear accessible even though they shouldn't be, e.g. because temporary pointers to them haven't yet been overwritten. Also note that by default only the first item in a chain of finalizable objects will be finalized in a collection.
    • Another GC_ call notices that there are finalizers waiting to be run and does so.

    Small test programs typically don't run long enough for this to happen.