Sesuatu yang ajaib terjadi setiap kali kita menulis del data: memori benar-benar bersih tanpa kita memanggil free() seperti di C. Sihirnya bukan sulap — Python menjalankan dua mekanisme: reference counting yang instan dan garbage collector (GC) siklis yang berkala. Pada Chapter ini kita bedah keduanya, membuktikan di mana reference counting gagal, dan melihat alat kendali yang disediakan module gc.
Reference Counting: Pencacah di Setiap Object #
Setiap object Python menyimpan satu angka penting di headernya: berapa banyak reference yang sedang menunjuk ke object tersebut. Penugasan nama menaikkan pencacah; nama yang dihapus atau ditimpa menurunkannya. Ketika pencacah mencapai nol, interpreter langsung membebaskan object pada saat itu juga — tidak menunggu jadwal apa pun.
Kita bisa mengintip pencacah ini dengan sys.getrefcount():
import sys
data = [1, 2, 3]
print("refcount data :", sys.getrefcount(data))
salinan = data
print("refcount setelah salinan:", sys.getrefcount(data))
del salinan
print("refcount setelah del :", sys.getrefcount(data))Output yang Dihasilkan:
refcount data : 2
refcount setelah salinan: 3
refcount setelah del : 2Mari kita telaah hasilnya:
- Baris pertama (
2): satu reference berasal dari namadata, satu lagi dari argumen pemanggilansys.getrefcount(data)sendiri — pemanggilan fungsi mengikat sementara satu reference tambahan. Karena itu hasil yang dilaporkan selalu satu lebih besar dari jumlah nama sebenarnya. - Baris kedua (
3): penugasansalinan = datatidak menyalin list; interpreter hanya menaikkan pencacah object yang sama. - Baris ketiga (
2):del salinanmenurunkan pencacah kembali — object-nya sendiri masih hidup karenadatamasih menunjuk ke object tersebut.
Masalah Siklus: Ketika Reference Counting Gagal #
Reference counting punya titik buta yang fatal. Bayangkan dua object yang saling menunjuk: A berisi reference ke B, B berisi reference ke A. Ketika nama luar terakhir dihapus, pencacah keduanya masih bernilai satu — satu dari pasangannya masing-masing. Padahal keduanya sudah tidak bisa dijangkau program mana pun. Mari kita buktikan:
import gc
class Simpul:
def __init__(self, nama):
self.nama = nama
self.tetangga = None
print("sebelum : jumlah objek terlacak :", len(gc.get_objects()))
a = Simpul("gudang")
b = Simpul("toko")
a.tetangga = b
b.tetangga = a
del a
del b
print("setelah del : jumlah objek terlacak :", len(gc.get_objects()))
hasil = gc.collect()
print("gc.collect() mengembalikan :", hasil)
print("setelah gc : jumlah objek terlacak :", len(gc.get_objects()))Output yang Dihasilkan (jumlah objek bervariasi antar mesin):
sebelum : jumlah objek terlacak : 5095
setelah del : jumlah objek terlacak : 5097
gc.collect() mengembalikan : 2
setelah gc : jumlah objek terlacak : 5008Tiga baris terakhir adalah intinya. Setelah del a dan del b, jumlah object terlacak tidak turun — kedua Simpul masih hidup saling bergandengan. Pemanggilan gc.collect() kemudian mengembalikan 2: dua object dalam siklus ditemukan dan dibebaskan. Garbage collector inilah yang menutup titik buta reference counting. Siklus nyata muncul antara lain pada dua-way link parent-anak, cache yang saling merujuk, atau callback yang menyimpan object pengirimnya.
Garbage Collector Generasional #
Menyapu seluruh heap setiap saat tentu mahal. Karena itu GC CPython memakai strategi generasional: semua object dilahirkan di generasi 0; object yang selamat dari beberapa pembersahan dipromosikan ke generasi 1, lalu ke generasi 2. Intuisinya sederhana — kebanyakan object mati muda (variable sementara di dalam function), jadi generasi muda disapu paling sering dan paling murah, sedangkan generasi tua yang sudah terbukti awet jarang diperiksa.
import gc
print("gc.get_threshold() :", gc.get_threshold())
print("gc.get_count() :", gc.get_count())
print("gc.get_stats() :")
for statistik in gc.get_stats():
print(" ", statistik)Output yang Dihasilkan:
gc.get_threshold() : (700, 10, 10)
gc.get_count() : (195, 5, 0)
gc.get_stats() :
{'collections': 5, 'collected': 24, 'uncollectable': 0}
{'collections': 0, 'collected': 0, 'uncollectable': 0}
{'collections': 0, 'collected': 0, 'uncollectable': 0}Tuple (700, 10, 10) dari gc.get_threshold() berarti: generasi 0 disapu setelah sekitar 700 alokasi neto baru; pembersahan generasi 0 yang ke-10 memicu sapuan generasi 1; sapuan generasi 1 yang ke-10 memicu sapuan generasi 2. gc.get_count() menunjukkan posisi pencacah saat ini — angkanya berubah terus seiring program berjalan, jadi jangan heran bila mesin Anda menampilkan nilai lain. Nilai collected pada gc.get_stats() mencatat berapa object siklis yang berhasil dibebaskan per generasi.
Jebakan __del__ dan Kontrol Manual #
Method __del__ dijalankan saat object dihapus — dan di sinilah letak jebakannya. Dalam sebuah siklus, urutan pemanggilan __del__ antar object tidak mudah diprediksi, dan sebelum Python 3.4 object bersiklus dengan __del__ bahkan bisa terkunci tak terbebaskan. Kode berikut mematikan GC terlebih dahulu agar kita bisa melihat dengan jelas kapan __del__ sebenarnya berjalan:
import gc
class Laporan:
def __del__(self):
print(f" __del__ dijalankan untuk {self.nama}")
gc.disable()
a = Laporan()
a.nama = "penjualan-2025"
b = Laporan()
b.nama = "pembelian-2025"
a.pasangan = b
b.pasangan = a
del a, b
print("objek sudah dihapus, sebelum gc.collect():")
hasil = gc.collect()
print("gc.collect() mengembalikan:", hasil)
gc.enable()Output yang Dihasilkan:
objek sudah dihapus, sebelum gc.collect():
__del__ dijalankan untuk penjualan-2025
__del__ dijalankan untuk pembelian-2025
gc.collect() mengembalikan: 2Perhatikan: setelah del a, b, tidak ada satu pun __del__ yang berjalan — reference counting tidak sanggup membebaskan siklus. Kedua __del__ baru dieksekusi ketika gc.collect() menyapu siklusnya. Pelajaran praktisnya: jangan menaruh logika penting (menutup file, koneksi database) hanya di __del__; gunakan context manager with yang sudah kita kenal dari Chapter Context Manager dan with, karena with menutup resource secara deterministik terlepas dari siklus.
Memutus Siklus dengan weakref #
Cara paling sehat menangani siklus adalah tidak membiarkannya menjadi kepemilikan penuh. Module weakref menyediakan reference yang tidak menaikkan reference count — ia menunjuk ke object tanpa menjaganya tetap hidup:
import gc
import weakref
class Pelanggan:
def __init__(self, nama):
self.nama = nama
pelanggan = Pelanggan("Budi")
referensi_lemah = weakref.ref(pelanggan)
print("objek masih hidup :", referensi_lemah() is not None)
print("nama via referensi :", referensi_lemah().nama)
del pelanggan
print("setelah del :", referensi_lemah())
print("gc.collect() mengembalikan :", gc.collect())Output yang Dihasilkan:
objek masih hidup : True
nama via referensi : Budi
setelah del : None
gc.collect() mengembalikan : 0Menggunakan referensi_lemah() mengembalikan object selama object masih hidup, dan None begitu object mati — tanpa perlu menunggu gc.collect() (perhatikan gc.collect() yang mengembalikan 0: tidak ada siklus yang perlu disapu). Pola umumnya: relasi kuat untuk kepemilikan (order memiliki daftar item), weakref untuk pengamatan (cache ringan, callback, pemetaan balik parent-anak). Class weakref.WeakValueDictionary dan weakref.WeakSet menyediakan container siap pakai dengan semangat yang sama.