Bỏ qua để đến nội dung

Quản lý bộ nhớ (Memory Management)

Python tự động quản lý bộ nhớ thông qua cơ chế Reference CountingGarbage Collection. Hiểu cách Python quản lý bộ nhớ giúp bạn viết code hiệu quả hơn và tránh memory leaks.

Trong Python, mọi thứ đều là object - kể cả số, chuỗi, hàm, class.

# Tất cả đều là objects
number = 42
text = "Hello"
my_list = [1, 2, 3]
# Kiểm tra ID của object (địa chỉ bộ nhớ)
print(id(number)) # Ví dụ: 140705844567328
print(id(text)) # Ví dụ: 2234567891234
print(id(my_list)) # Ví dụ: 2234567898765
# Mỗi object có type
print(type(number)) # <class 'int'>
print(type(text)) # <class 'str'>
print(type(my_list)) # <class 'list'>

Biến trong Python không chứa giá trị trực tiếp, mà chứa tham chiếu (reference) đến object.

# 'a' tham chiếu đến object số 100
a = 100
# 'b' tham chiếu đến cùng object với 'a'
b = a
print(id(a)) # 140705844567328
print(id(b)) # 140705844567328 (cùng ID)
print(a is b) # True (cùng object)
# Gán lại 'a' - tạo tham chiếu mới
a = 200
print(id(a)) # 140705844567360 (ID khác)
print(id(b)) # 140705844567328 (vẫn giữ ID cũ)
print(a is b) # False
# list1 tham chiếu đến một list object
list1 = [1, 2, 3]
print(id(list1)) # Ví dụ: 2234567898765
# list2 tham chiếu đến CÙNG object với list1
list2 = list1
print(id(list2)) # 2234567898765 (cùng ID)
# Thay đổi qua list2
list2.append(4)
# list1 cũng thay đổi (vì cùng object)
print(list1) # [1, 2, 3, 4]
print(list2) # [1, 2, 3, 4]
# Tạo list mới (copy)
list3 = list1.copy()
print(id(list3)) # ID khác (object mới)
list3.append(5)
print(list1) # [1, 2, 3, 4] (không đổi)
print(list3) # [1, 2, 3, 4, 5]

Python theo dõi số lượng tham chiếu đến mỗi object. Khi số tham chiếu về 0, object được giải phóng.

import sys
# Tạo object
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2 (1 từ 'a', 1 từ getrefcount)
# Tạo thêm tham chiếu
b = a
print(sys.getrefcount(a)) # 3 (a, b, getrefcount)
c = a
print(sys.getrefcount(a)) # 4 (a, b, c, getrefcount)
# Xóa tham chiếu
del b
print(sys.getrefcount(a)) # 3 (a, c, getrefcount)
del c
print(sys.getrefcount(a)) # 2 (a, getrefcount)
# Khi del a, reference count = 0 → object bị xóa
import sys
# Object với 1 reference
x = "Hello"
print(sys.getrefcount(x)) # Có thể > 2 (Python cache chuỗi)
# Object trong container
my_list = [1, 2, 3]
container = [my_list, my_list, my_list]
print(sys.getrefcount(my_list)) # 5 (my_list, 3 refs trong container, getrefcount)
# Xóa container
del container
print(sys.getrefcount(my_list)) # 2 (chỉ còn my_list và getrefcount)

Reference counting không xử lý được circular references (tham chiếu vòng).

import sys
class Node:
def __init__(self, value):
self.value = value
self.next = None
# Tạo circular reference
node1 = Node(1)
node2 = Node(2)
node1.next = node2 # node1 → node2
node2.next = node1 # node2 → node1 (vòng!)
print(sys.getrefcount(node1)) # 3 (node1, node2.next, getrefcount)
print(sys.getrefcount(node2)) # 3 (node2, node1.next, getrefcount)
# Xóa node1 và node2
del node1
del node2
# Reference count vẫn > 0 (vì tham chiếu lẫn nhau)
# → Reference counting KHÔNG giải phóng được
# → Cần Garbage Collector

Python có bộ Garbage Collector tự động phát hiện và xóa circular references.

import gc
# Xem cấu hình garbage collector
print(gc.get_threshold()) # (700, 10, 10)
# 700: số object mới trước khi chạy generation 0 collection
# 10: tần suất collection cho generation 1
# 10: tần suất collection cho generation 2
# Xem số lượng collections đã chạy
print(gc.get_count()) # (số object, số collections gen0, gen1, gen2)
# Chạy garbage collection thủ công
collected = gc.collect()
print(f"Collected {collected} objects")
# Kiểm tra GC có bật không
print(gc.isenabled()) # True
# Tắt GC (KHÔNG KHUYẾN KHÍCH)
# gc.disable()
# Bật lại GC
# gc.enable()

Python GC sử dụng 3 generations (thế hệ):

import gc
# Generation 0: Objects mới tạo
# Generation 1: Objects sống sót qua collection gen 0
# Generation 2: Objects sống sót qua collection gen 1
# Xem thống kê
stats = gc.get_stats()
for i, stat in enumerate(stats):
print(f"Generation {i}: {stat}")
# Ví dụ output:
# Generation 0: {'collections': 45, 'collected': 1234, 'uncollectable': 0}
# Generation 1: {'collections': 4, 'collected': 56, 'uncollectable': 0}
# Generation 2: {'collections': 0, 'collected': 0, 'uncollectable': 0}

Immutable objects không thể thay đổi sau khi tạo. Python tối ưu bằng cách tái sử dụng object.

# Số nguyên nhỏ (-5 đến 256) được cache
a = 10
b = 10
print(a is b) # True (cùng object)
print(id(a) == id(b)) # True
# Số lớn hơn không được cache (có thể khác nhau)
x = 1000
y = 1000
print(x is y) # False (Python 3.8+: có thể True trong cùng dòng)
print(id(x) == id(y)) # False
# Chuỗi ngắn được interned (cache)
s1 = "hello"
s2 = "hello"
print(s1 is s2) # True (cùng object)
# Chuỗi có ký tự đặc biệt có thể không được interned
s3 = "hello world!"
s4 = "hello world!"
print(s3 is s4) # Có thể False (tùy Python version)
# Tuple là immutable
t = (1, 2, 3)
# t[0] = 10 # Lỗi: 'tuple' object does not support item assignment

Mutable objects có thể thay đổi mà không tạo object mới.

# List là mutable
list1 = [1, 2, 3]
id_before = id(list1)
list1.append(4) # Thay đổi object hiện tại
id_after = id(list1)
print(id_before == id_after) # True (vẫn là object cũ)
# Dictionary là mutable
dict1 = {"a": 1}
id_before = id(dict1)
dict1["b"] = 2
id_after = id(dict1)
print(id_before == id_after) # True
# Set là mutable
set1 = {1, 2, 3}
id_before = id(set1)
set1.add(4)
id_after = id(set1)
print(id_before == id_after) # True
# Immutable: Tạo object mới → tốn memory nếu thay đổi nhiều
s = "a"
for i in range(5):
s += "a" # Tạo string mới mỗi lần
print(id(s)) # ID khác nhau mỗi lần
# Mutable: Thay đổi object hiện tại → tiết kiệm memory
lst = ["a"]
id_original = id(lst)
for i in range(5):
lst.append("a") # Không tạo object mới
print(id(lst) == id_original) # True mọi lần

Copy nông tạo object mới nhưng không copy các object bên trong.

import copy
# List lồng nhau
original = [[1, 2], [3, 4]]
# Shallow copy
shallow = copy.copy(original)
# Hoặc: shallow = original.copy()
# Hoặc: shallow = original[:]
print(id(original)) # ID khác
print(id(shallow)) # ID khác
# Nhưng các list bên trong vẫn là cùng object
print(id(original[0])) # ID giống
print(id(shallow[0])) # ID giống
print(original[0] is shallow[0]) # True
# Thay đổi list con
shallow[0].append(5)
print(original) # [[1, 2, 5], [3, 4]] - BỊ THAY ĐỔI!
print(shallow) # [[1, 2, 5], [3, 4]]

Deep copy tạo object mới và copy đệ quy tất cả objects bên trong.

import copy
# List lồng nhau
original = [[1, 2], [3, 4]]
# Deep copy
deep = copy.deepcopy(original)
print(id(original)) # ID khác
print(id(deep)) # ID khác
# Các list bên trong cũng khác nhau
print(id(original[0])) # ID khác
print(id(deep[0])) # ID khác
print(original[0] is deep[0]) # False
# Thay đổi list con
deep[0].append(5)
print(original) # [[1, 2], [3, 4]] - KHÔNG THAY ĐỔI
print(deep) # [[1, 2, 5], [3, 4]]
import copy
class Person:
def __init__(self, name, hobbies):
self.name = name
self.hobbies = hobbies # list
# Original
alice = Person("Alice", ["reading", "coding"])
# Shallow copy
alice_shallow = copy.copy(alice)
alice_shallow.name = "Alice Clone" # OK - string immutable
alice_shallow.hobbies.append("gaming") # ẢNH HƯỞNG alice!
print(alice.name) # Alice (không đổi)
print(alice.hobbies) # ['reading', 'coding', 'gaming'] - BỊ THAY ĐỔI!
# Deep copy
bob = Person("Bob", ["swimming", "running"])
bob_deep = copy.deepcopy(bob)
bob_deep.name = "Bob Clone"
bob_deep.hobbies.append("cycling") # KHÔNG ảnh hưởng bob
print(bob.name) # Bob (không đổi)
print(bob.hobbies) # ['swimming', 'running'] - KHÔNG THAY ĐỔI
print(bob_deep.hobbies) # ['swimming', 'running', 'cycling']
import sys
# Kích thước của object
print(sys.getsizeof(1)) # 28 bytes (int)
print(sys.getsizeof("a")) # 50 bytes (str)
print(sys.getsizeof([])) # 56 bytes (empty list)
print(sys.getsizeof([1, 2, 3])) # 80 bytes (list with 3 items)
print(sys.getsizeof({})) # 64 bytes (empty dict)
# Chú ý: getsizeof() chỉ đo kích thước object,
# KHÔNG bao gồm objects mà nó tham chiếu đến
my_list = [[1, 2, 3], [4, 5, 6]]
print(sys.getsizeof(my_list)) # Chỉ đo list ngoài
# Không tính kích thước của [1, 2, 3] và [4, 5, 6]

Đo tổng kích thước (bao gồm nested objects)

Phần tiêu đề “Đo tổng kích thước (bao gồm nested objects)”
import sys
def get_size(obj, seen=None):
"""Tính tổng kích thước bao gồm cả nested objects"""
size = sys.getsizeof(obj)
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
seen.add(obj_id)
if isinstance(obj, dict):
size += sum([get_size(v, seen) for v in obj.values()])
size += sum([get_size(k, seen) for k in obj.keys()])
elif hasattr(obj, '__dict__'):
size += get_size(obj.__dict__, seen)
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
size += sum([get_size(i, seen) for i in obj])
return size
# Test
data = {
'numbers': [1, 2, 3, 4, 5],
'nested': {
'list': [10, 20, 30],
'dict': {'a': 1, 'b': 2}
}
}
print(f"Shallow size: {sys.getsizeof(data)} bytes")
print(f"Total size: {get_size(data)} bytes")
# 1. Xóa biến không dùng nữa (giải phóng memory)
big_list = list(range(1000000))
# ... xử lý xong
del big_list
# 2. Dùng generator thay vì list (tiết kiệm memory)
# ❌ Tốn memory
squares_list = [x**2 for x in range(1000000)]
# ✅ Tiết kiệm memory
squares_gen = (x**2 for x in range(1000000))
# 3. Đọc file lớn từng dòng
# ❌ Tốn memory
with open('large_file.txt') as f:
lines = f.readlines() # Đọc hết vào memory
# ✅ Tiết kiệm memory
with open('large_file.txt') as f:
for line in f: # Đọc từng dòng
process(line)
# 4. Dùng __slots__ cho class nhiều instances
class Point:
__slots__ = ['x', 'y'] # Giảm memory consumption
def __init__(self, x, y):
self.x = x
self.y = y
# 5. Clear cache khi không cần
import functools
@functools.lru_cache(maxsize=128)
def expensive_function(n):
return n ** 2
# Khi không cần cache nữa
expensive_function.cache_clear()
# 1. Tạo list lớn không cần thiết
# ❌
result = []
for i in range(1000000):
result.append(i * 2)
# ✅ Dùng generator
def generate_results():
for i in range(1000000):
yield i * 2
# 2. Circular references không cần thiết
# ❌
class Node:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
# Cẩn thận với parent/children references
# 3. Giữ references đến objects lớn
# ❌
cache = {}
def process_data(data):
cache[id(data)] = data # Giữ reference → không giải phóng
# ...
import copy
original = {
'name': 'John',
'scores': [85, 90, 92],
'address': {
'city': 'NYC',
'zipcode': '10001'
}
}
shallow = copy.copy(original)
deep = copy.deepcopy(original)
# Thay đổi scores
shallow['scores'].append(95)
print("Original:", original['scores'])
print("Shallow:", shallow['scores'])
print("Deep:", deep['scores'])
# Kết quả:
# Original: [85, 90, 92, 95] - BỊ THAY ĐỔI
# Shallow: [85, 90, 92, 95]
# Deep: [85, 90, 92] - KHÔNG THAY ĐỔI
import sys
# ❌ Không hiệu quả
def process_numbers_list(n):
numbers = [x for x in range(n)]
squares = [x**2 for x in numbers]
return sum(squares)
# ✅ Hiệu quả hơn
def process_numbers_gen(n):
return sum(x**2 for x in range(n))
n = 1000000
# So sánh memory
list_result = process_numbers_list(n)
gen_result = process_numbers_gen(n)
print(f"Results equal: {list_result == gen_result}")
  • Reference Counting: Python đếm số tham chiếu đến mỗi object
  • Garbage Collection: Tự động phát hiện và xóa circular references
  • Immutable objects: int, float, str, tuple, frozenset - không thay đổi được
  • Mutable objects: list, dict, set - có thể thay đổi
  • Shallow copy: Copy object nhưng không copy nested objects
  • Deep copy: Copy đệ quy tất cả objects
  • Best practices:
    • Xóa biến không dùng với del
    • Dùng generators thay vì lists khi xử lý dữ liệu lớn
    • Cẩn thận với circular references
    • Sử dụng __slots__ cho classes có nhiều instances
    • Đọc file lớn từng dòng thay vì đọc hết vào memory