3
votes

I was reading the documentation when I came in doubt with the following phrase:

Since the collector supplements the reference counting already used in Python, you can disable the collector if you are sure your program does not create reference cycles.

What does this mean? If I disable the garbage collector (gc.disable()) and I do something like this:

a = 'hi'
a = 'hello'

will 'hi' remain in memory? Do I need to free the memory by myself?

What I understood from that sentence is that the gc is an extra tool made up expecially to catch reference cycles and if it is disabled the memory is still automatically cleaned using the reference counters of the objects but the reference cycles will not be managed. Is that right?

3
In your example'hi' will indeed remain in memory, but attempting to free it would be wrong. Being a string literal it'll be interned and stored as a constant (in the code object). But that probably wasn't your question. I suggest you choose a more innocent example (something involving lists perhaps) to divert attention from that issue. - user395760
So if a was referenced to something more complex, instead a simple string literal, will that 'something' remain in memory? Can't I do something to free the space in memory? - zer0uno
No, quite the opposite. That a string literal would remain in memory has nothing to do with the cycle GC being enabled or the complexity of the data, the string object simply remains reachable and hence wouldn't even be collected by the cycle GC. This is only because you used a string literal as example. Other data isn't silently referenced from elsewhere and becomes unreachable and is freed in example like a = [1]; a = something else. - user395760

3 Answers

3
votes

In CPython, objects are cleared from memory immediately when their reference count drops to 0.

The moment you rebind a to 'hello', the reference count for the 'hi' string object is decremented. If it reaches 0, it'll be removed from memory.

As such, the garbage collector only needs to deal with objects that (indirectly or directly) reference one another, and thus keep the reference count from ever dropping to 0.

Strings cannot reference other objects, so are not of interest to the garbage collector. But anything that can reference something else (such as containers types such as lists or dictionaries, or any Python class or instance) can produce a circular reference:

a = []  # Ref count is 1
a.append(a)  # A circular reference! Ref count is now 2
del a   # Ref count is decremented to 1

The garbage collector detects these circular references; nothing else references a, so eventually the gc process breaks the circle, letting the reference counts drop to 0 naturally.

Incidentally, the Python compiler bundles string literals such as 'hi' and 'hello' as constants with the bytecode produced and as such, there is always at least one reference to such objects. In addition, string literals used in source code that match the regular expression [a-zA-Z0-9_] are interned; made into singletons to reduce the memory footprint, so other code blocks that use the same string literal will hold a reference to the same shared string.

1
votes

You understanding of the docs is correct (but see caveat below).

Reference counting still works when GC is disabled. In other words, circular references will not be resolved, but if the reference count for an object drops to zero, the object will be GC'd.

Caveat: note that this doesn't apply to small strings (and integers) that are treated differently from other objects in Python (they're not really GC'd) — see Martijn Pieters' answer for more detail.

Consider the following code

import weakref
import gc


class Test(object):
    pass


class Cycle(object):
    def __init__(self):
        self.other = None


if __name__ == '__main__':
    gc.disable()


    print "-- No Cycle"
    t = Test()

    r_t = weakref.ref(t)  # Weak refs don't increment refcount
    print "Before re-assign"
    print r_t()
    t = None
    print "After re-assign"
    print r_t()

    print

    print "-- Cycle"
    c1 = Cycle()
    c2 = Cycle()
    c1.other = c2
    c2.other = c1

    r_c1 = weakref.ref(c1)
    r_c2 = weakref.ref(c2)

    c1 = None
    c2 = None

    print "After re-assign"

    print r_c1()
    print r_c2()

    print "After run GC"
    gc.collect()
    print r_c1()
    print r_c2()

Its output is :

-- No Cycle
Before re-assign
<__main__.Test object at 0x101387e90>   # The object exists
After re-assign
None  # The object was GC'd

-- Cycle
After re-assign
<__main__.Cycle object at 0x101387e90>  # The object wasn't GC'd due to the circular reference
<__main__.Cycle object at 0x101387f10>
After run GC
None  # The GC was able to resolve the circular reference, and deleted the object
None
0
votes

In your example "hi" does not remain in memory. The garbage collector detects Circular references.

Here is a simple example of a circular reference in python:

a = []
b = [a]
a.append(b)

Here a contains b and b contains a. If you disable the garbage collector these two objects will remain in memory.

Note that some of the built-in modules cause circular references. And it's usually not worth disabling it.