Contents
- 1 1. Cấu trúc và cơ chế quản lý
- 2 2. Thay đổi con trỏ stack là gì?
- 2.1 Ví dụ: Cấp phát và giải phóng bộ nhớ trên stack
- 2.1.1 2.1. Phân tích với một method đơn giản:
- 2.1.2 2.2. Quy trình cấp phát bộ nhớ trong Stack
- 2.1.3 2.3. Giải phóng bộ nhớ khi hàm hoàn thành
- 2.1.4 2.4. Quy trình cấp phát cho phương thức tiếp theo
- 2.1.5 2.5. Không ghi đè dữ liệu nếu không dùng
- 2.1.6 Khi ExampleMethod kết thúc
- 2.1.7 Khi AnotherMethod chạy
- 2.1 Ví dụ: Cấp phát và giải phóng bộ nhớ trên stack
- 3 3. Ví dụ minh họa 2
- 4 4. Cấp phát bộ nhớ
- 5 5. Vị trí trong bộ nhớ và khả năng truy cập
- 6 6. Chi phí quản lý bộ nhớ
- 7 7. Ứng dụng thực tế
1. Cấu trúc và cơ chế quản lý
- Stack Memory:
- Stack là một vùng bộ nhớ có cấu trúc lifo (last in, first out).
- Khi một biến được khai báo (ví dụ: biến local hoặc biến trong hàm), nó được thêm trực tiếp vào stack. Khi biến không còn trong phạm vi (scope), nó sẽ tự động bị loại bỏ mà không cần sự can thiệp của trình quản lý bộ nhớ.
- Việc cấp phát và giải phóng bộ nhớ trong stack rất nhanh, vì nó chỉ cần thay đổi con trỏ stack để quản lý các giá trị.
- Heap Memory:
- Heap là một vùng bộ nhớ lớn hơn và phức tạp hơn.
- Khi một đối tượng được khởi tạo trên heap (thường bằng từ khóa
new
), bộ nhớ cần phải được quản lý thông qua Garbage Collector (GC), điều này làm chậm quá trình truy cập do GC cần thực hiện các bước thu thập, dọn dẹp, và tối ưu hóa bộ nhớ.

2. Thay đổi con trỏ stack là gì?
Trong Stack Memory, hệ điều hành sử dụng một con trỏ gọi là Stack Pointer (SP) để đánh dấu vị trí hiện tại của đỉnh stack. Mỗi khi một biến hoặc dữ liệu được thêm vào stack, con trỏ này dịch chuyển xuống dưới (tăng kích thước stack). Ngược lại, khi một biến ra khỏi phạm vi (scope) hoặc hàm hoàn thành, con trỏ dịch chuyển lên trên để loại bỏ dữ liệu.
Ví dụ: Cấp phát và giải phóng bộ nhớ trên stack
2.1. Phân tích với một method đơn giản:
void ExampleMethod()
{
int a = 10; // Biến local 1
float b = 20.5f; // Biến local 2
int c = a + 5; // Biến local 3
}
Khi phương thức ExampleMethod()
được gọi, các bước xảy ra như sau:
2.2. Quy trình cấp phát bộ nhớ trong Stack
- Khi hàm được gọi:
- Stack Memory được chuẩn bị để lưu dữ liệu của hàm.
- Bộ nhớ dành cho các biến local
a
,b
,c
sẽ được cấp phát tuần tự từ trên xuống.
- Dữ liệu lưu trữ:
- Con trỏ stack bắt đầu tại một vị trí (giả sử
SP = X
). - Khi biến
a
được khai báo, stack cấp phát một phần bộ nhớ (4 bytes cho kiểuint
).SP
được thay đổi:SP = X - 4
.
- Tiếp theo, biến
b
được khai báo (4 bytes cho kiểufloat
).SP
tiếp tục giảm:SP = X - 8
.
- Cuối cùng,
c
được tính toán và lưu trữ (4 bytes cho kiểuint
).SP
giảm thêm:SP = X - 12
.
- Con trỏ stack bắt đầu tại một vị trí (giả sử
- Bộ nhớ stack sau khi cấp phát:
(Stack bắt đầu từ địa chỉ cao và giảm dần)
| 4 bytes (c = 15) | (SP=X-12)
|------------------------------|
| 4 bytes (b = 20.5) | (SP=X-8)
|------------------------------|
| 4 bytes (a = 10) | (SP=X-4)
|------------------------------| (SP=X)
2.3. Giải phóng bộ nhớ khi hàm hoàn thành
Khi phương thức ExampleMethod()
kết thúc:
- Toàn bộ các biến local (
a
,b
,c
) không còn cần thiết. - Con trỏ stack (
SP
) trở về vị trí ban đầu (tăng lên lạiSP = X
), mà không cần xóa dữ liệu từng biến cụ thể. - Phần bộ nhớ này sẵn sàng được sử dụng lại cho các hàm khác.
2.4. Quy trình cấp phát cho phương thức tiếp theo
Giả sử bạn có một phương thức mới như sau:
void AnotherMethod()
{
short x = 5; // 2 bytes
int y = 10; // 4 bytes
}
- Khi
AnotherMethod
được gọi, nó cần 6 bytes (2 bytes chox
và 4 bytes choy
). - Stack pointer sẽ giảm thêm 6 bytes so với vị trí hiện tại của nó.
- Nếu không có bất kỳ ghi đè nào xảy ra (do hành động của
AnotherMethod
), dữ liệu cũ (củaExampleMethod
) có thể vẫn tồn tại trong stack nhưng sẽ không ảnh hưởng vì:- Các biến của
ExampleMethod
không còn trong phạm vi (scope). - Chỉ
AnotherMethod
có quyền truy cập tới vùng nhớ mới được cấp phát (6 bytes).
- Các biến của
2.5. Không ghi đè dữ liệu nếu không dùng
Khi stack pointer di chuyển để cấp phát cho AnotherMethod
, các khu vực không sử dụng của stack sẽ chỉ được ghi đè khi:
- Biến mới được khởi tạo và ghi giá trị vào bộ nhớ.
- Nếu vùng nhớ đó không được ghi đè ngay, dữ liệu cũ (như từ
ExampleMethod
) vẫn còn nhưng không được sử dụng vì không có cách nào tham chiếu đến nó.
Dưới đây là cách stack hoạt động:
Ban đầu: Khi ExampleMethod
chạy
| 4 bytes (c = 15) |
|--------------------------|
| 4 bytes (b = 20.5) |
|--------------------------|
| 4 bytes (a = 10) |
|--------------------------|
Khi ExampleMethod
kết thúc
- Stack pointer trở về vị trí trước khi
ExampleMethod
được gọi. - Bộ nhớ của
a
,b
,c
vẫn tồn tại nhưng được đánh dấu là không hợp lệ.
Khi AnotherMethod
chạy
| 4 bytes (y = 10) | <- `AnotherMethod` sử dụng 4 bytes cho `y`
|--------------------------|
| 2 bytes (x = 5) | <- `AnotherMethod` sử dụng 2 bytes cho `x`
|--------------------------|
| (Dữ liệu cũ, không dùng)|
|--------------------------|
x
vày
ghi đè lên một phần bộ nhớ củaExampleMethod
, nhưng chỉ ở những nơi mà chúng được cấp phát.
3. Ví dụ minh họa 2

Giải thích mã nguồn
a. Khai báo và khởi tạo Person p
Person p = new Person();
- Bước 1:
Person p
được khai báo- Biến
p
được khai báo trong phương thứcMain
. - Biến này là một tham chiếu (reference), được lưu trữ trong Stack Memory.
- Ban đầu,
p
không trỏ đến bất kỳ đối tượng nào trên Heap.
- Biến
- Bước 2:
new Person()
- Lệnh
new Person()
khởi tạo một đối tượng mới thuộc lớpPerson
. - Đối tượng này được lưu trong Heap Memory.
- Mặc định:
- Thuộc tính
name
(kiểustring
) được khởi tạo với giá trịnull
. - Thuộc tính
age
(kiểuint
) được khởi tạo với giá trị0
.
- Thuộc tính
- Lệnh
- Bước 3: Gán giá trị cho
p
- Giá trị trả về từ
new Person()
là địa chỉ của vùng nhớ trên Heap, nơi đối tượngPerson
được lưu trữ. - Địa chỉ này được gán cho biến
p
trong Stack. - Kết quả:
p
lưu địa chỉ của đối tượng trên Heap.
- Giá trị trả về từ
b. Kết quả lưu trữ trong bộ nhớ
Sau khi thực hiện lệnh Person p = new Person();
, trạng thái bộ nhớ sẽ như sau:
Stack Memory:
- Biến
p
được lưu trên Stack. - Giá trị của
p
là địa chỉ tham chiếu (ví dụ:0x123456
) trỏ đến đối tượng trên Heap.
Heap Memory:
- Đối tượng
Person
được lưu trữ trong Heap, với các thuộc tính:name = null
(kiểustring
ban đầu là null vì chưa được khởi tạo).age = 0
(kiểuint
mặc định là 0).
c. Diễn giải bộ nhớ qua hình minh họa
Dựa trên hình ảnh và mã nguồn:
Stack:
- Lưu trữ biến cục bộ
p
:
p -> 0x123456 (địa chỉ trỏ tới đối tượng trên Heap)
Heap:
- Lưu trữ đối tượng
Person
:
Person
-------
name = null
age = 0
- Đối tượng này được lưu tại địa chỉ 0x123456 trên Heap, và biến
p
trên Stack trỏ tới địa chỉ này.
d. Khi phương thức Main
kết thúc
- Stack:
- Biến
p
trên Stack bị xóa, vì phạm vi của phương thứcMain
đã kết thúc.
- Biến
- Heap:
- Đối tượng
Person
trên Heap không được giải phóng ngay lập tức. - Nó sẽ trở thành “rác” nếu không còn tham chiếu nào trỏ đến nó.
- Garbage Collector (GC) sẽ thu gom bộ nhớ của đối tượng này trong một chu kỳ thu gom rác sau đó.
- Đối tượng
4. Cấp phát bộ nhớ
Ví dụ minh họa
Mã nguồn:
int[] arrs = new int[1000];
Phân tích cấp phát bộ nhớ:
int[] arrs
(biến tham chiếu):- Biến
arrs
là một reference type, được lưu trên Stack. - Nó lưu địa chỉ của mảng
int[1000]
trên Heap.
- Biến
new int[1000]
(khởi tạo mảng):- Mảng
int[1000]
được tạo trên Heap Memory. - Bộ nhớ trên Heap được cấp phát để lưu trữ 1000 phần tử, mỗi phần tử là một
int
(4 byte trên hệ thống 32-bit hoặc 64-bit).
- Mảng
- Tổng kích thước bộ nhớ cấp phát:
- Stack Memory:
- Biến
arrs
lưu trữ một tham chiếu (địa chỉ) trỏ tới mảng trên Heap. - Ví dụ:
arrs -> 0x123456
(địa chỉ của mảng trên Heap).
- Biến
- Heap Memory:
- Mảng
int[1000]
chiếm:1000 × 4 byte = 4000 byte (4 KB).
- Bộ nhớ này được quản lý bởi Garbage Collector.
- Mảng
- Stack Memory:
Cách lưu trữ bộ nhớ:
- Stack:
arrs -> 0x123456
(Biếnarrs
trỏ tới địa chỉ của mảng trên Heap.) - Heap:
0x123456: [int(0)][int(0)][int(0)] ... [int(0)]
(Heap lưu mảngint[1000]
, mỗi phần tử được khởi tạo với giá trị mặc định là0
.)
Hành vi bộ nhớ khi kết thúc phạm vi
- Khi phạm vi sử dụng biến
arrs
kết thúc (ví dụ: khi phương thức chứa dòng lệnh trên kết thúc):- Stack:
- Biến
arrs
bị xóa khỏi Stack.
- Biến
- Heap:
- Mảng
int[1000]
vẫn nằm trên Heap, nhưng không còn tham chiếu nào trỏ đến nó. - Garbage Collector (GC) sẽ thu gom mảng này trong một chu kỳ thu gom rác tiếp theo.
- Mảng
- Stack:
5. Vị trí trong bộ nhớ và khả năng truy cập
- Stack Memory có vị trí liền mạch trong RAM, do đó CPU có thể truy cập nhanh hơn thông qua địa chỉ trực tiếp.
- Heap Memory thường bị phân mảnh do việc cấp phát và giải phóng bộ nhớ không đồng đều. Điều này dẫn đến thời gian tìm kiếm và truy cập lâu hơn so với stack.
Dung lượng Stack Memory - Kích thước stack được giới hạn cố định bởi hệ điều hành hoặc môi trường runtime:
- Windows: Thông thường, mỗi luồng (thread) được cấp khoảng 1 MB stack memory.
- Unity (C#): Kích thước mặc định của stack là 1 MB cho mỗi luồng (thread) chính.
- Nếu bộ nhớ stack bị sử dụng vượt quá giới hạn này, sẽ xảy ra Stack Overflow.
6. Chi phí quản lý bộ nhớ
- Stack Memory:
- Không có chi phí quản lý lớn, vì bộ nhớ được tự động cấp phát và giải phóng dựa trên phạm vi của hàm hoặc khối lệnh.
- Do đó, hiệu năng cao hơn khi truy cập.
- Heap Memory:
- Yêu cầu dynamic memory allocation (cấp phát bộ nhớ động) và cần Garbage Collector để quản lý việc thu hồi bộ nhớ.
- Quá trình này làm tăng chi phí và ảnh hưởng đến hiệu năng.
7. Ứng dụng thực tế
- Stack Memory thường được dùng cho các biến giá trị (value type) hoặc dữ liệu tạm thời như các biến trong hàm.
- Heap Memory phù hợp với các đối tượng phức tạp hoặc dữ liệu cần tồn tại trong thời gian dài, chẳng hạn như reference type (object, string, array…).