Skip to content

Memory Management Overview

Python의 메모리 관리는 두 가지 메커니즘이 함께 동작합니다.

Mental Model

Python uses a two-layer cleanup system. Reference counting is the fast, automatic first pass -- the moment nothing points to an object, it is freed instantly. The garbage collector is the slower second pass that catches the edge case reference counting misses: circular references where objects keep each other alive with no outside connections.

Two Mechanisms

1. Reference Counting

대부분의 객체는 참조 카운팅으로 즉시 해제됩니다.

```python import sys

x = [1, 2, 3] print(sys.getrefcount(x)) # Count references ```

2. Garbage Collection

순환 참조는 주기적인 가비지 컬렉션으로 처리됩니다.

```python import gc

Handle cycles

gc.collect() ```

How They Work Together

Object Created │ ▼ ┌─────────────────┐ │ Reference Count │ ──→ refcount == 0 ──→ Freed Immediately └─────────────────┘ │ ▼ (refcount > 0 but unreachable) ┌─────────────────┐ │ Cycle GC │ ──→ Detects cycles ──→ Freed └─────────────────┘

  • Refcount: 즉각적인 메모리 해제 (대부분의 경우)
  • GC: 순환 참조 처리 (주기적 실행)

Summary

  • Reference counting: immediate, deterministic
  • Garbage collection: handles cycles
  • Both work automatically

Exercises

Exercise 1. Write a script that creates an object, prints its reference count using sys.getrefcount(), then adds it to a list and a dictionary. Print the reference count after each step, then remove it from both containers and print the final count. Explain in comments why getrefcount() reports one more than expected.

Solution to Exercise 1
```python
import sys

obj = [1, 2, 3]
# getrefcount adds 1 temporary reference for the argument
print(f"Initial: {sys.getrefcount(obj)}")  # 2

lst = [obj]
print(f"After list: {sys.getrefcount(obj)}")  # 3

d = {"key": obj}
print(f"After dict: {sys.getrefcount(obj)}")  # 4

lst.remove(obj)
print(f"After removing from list: {sys.getrefcount(obj)}")  # 3

del d["key"]
print(f"After removing from dict: {sys.getrefcount(obj)}")  # 2
```

Exercise 2. Create two Node objects that form a circular reference. Use gc.collect() to clean them up and print the number of collected objects. Then repeat the experiment with gc.disable() before creating the cycle, and show that del alone does not free them (verify by checking gc.garbage or object counts).

Solution to Exercise 2
```python
import gc

class Node:
    def __init__(self, name):
        self.name = name
        self.ref = None

# With GC enabled
gc.enable()
a = Node("A")
b = Node("B")
a.ref = b
b.ref = a
del a, b
collected = gc.collect()
print(f"GC enabled - collected: {collected}")

# With GC disabled
gc.disable()
c = Node("C")
d = Node("D")
c.ref = d
d.ref = c
del c, d
# Without gc.collect(), cycles are not freed
before = len(gc.get_objects())
gc.enable()
collected = gc.collect()
after = len(gc.get_objects())
print(f"GC disabled then collected: {collected}")
```

Exercise 3. Write a function show_memory_mechanisms() that demonstrates both memory management mechanisms in sequence: (a) create and delete a simple list, showing immediate deallocation via reference counting (check weakref.ref returns None), and (b) create a circular reference, delete the variables, and show the objects persist until gc.collect() is called.

Solution to Exercise 3
```python
import weakref
import gc

def show_memory_mechanisms():
    # (a) Reference counting: immediate deallocation
    class Temp:
        pass

    obj = Temp()
    ref = weakref.ref(obj)
    print(f"Before del: ref() is None = {ref() is None}")
    del obj
    print(f"After del:  ref() is None = {ref() is None}")
    print("-> Reference counting freed the object immediately\n")

    # (b) Circular reference: needs GC
    class Node:
        def __init__(self):
            self.ref = None

    a = Node()
    b = Node()
    a.ref = b
    b.ref = a
    ref_a = weakref.ref(a)

    del a, b
    print(f"After del (cycle): ref_a() is None = {ref_a() is None}")

    collected = gc.collect()
    print(f"After gc.collect(): ref_a() is None = {ref_a() is None}")
    print(f"-> GC collected {collected} objects from the cycle")

show_memory_mechanisms()
```