Bộ nhớ Heap và bộ nhớ Stack đều là một phần của JVM dùng để thực thi chương trình Java. Khi chương trình được thực thi JVM sẽ yêu cầu hệ điều hành cấp phát bộ nhớ trong Ram để chạy, JVM sẽ chia thành bộ nhớ Heap và bộ nhớ Stack để quản lý.

Bộ nhớ Heap

bài trước chúng ta đã biết bộ nhớ Heap dùng để lưu trữ tất cả các đối tượng được tạo ra trong quá trình thực thi ứng dụng (sử dụng toán tử new).

Objects trong bộ nhớ Heap đều được truy cập bởi mọi nơi trong ứng dụng, mọi threads khác nhau. Thời gian tồn tại của objects phụ thuộc vào GC (trình thu thập rác tự động). Các objects không được sử dụng trong Heap sẽ được GC loại bỏ và trả lại bộ nhớ cho Heap.

Dung lượng bộ nhớ của Heap phụ thuộc vào số lượng Objects sử dụng. Dung lượng bộ nhớ Heap thường lớn hơn dung lượng bộ nhớ Stack. Cơ chế quản lý của Heap chia ra làm hai loại Young-Generation và Old-Generation (tìm hiểu trong bài tiếp theo)

Bộ nhớ Stack

Bộ nhớ stack dùng để lưu các biến cục bộ trong hàm và lời gọi hàm ở runtime trong một thread java, theo từng luồng riêng biệt nhau và không được sử dụng lẫn nhau giữa các luồng khác nhau.

Các biến local bao gồm kiểu nguyên thuỷ (primitive) và kiểu tham chiếu tới đối tượng trong heap (reference) khai báo trong hàm, hoặc đối số được truyền vào hàm, thường có thời gian sống ngắn.

Hiểu đơn giản chỗ này nghĩa là tất cả những biến được khai báo hoặc truyền đối số trong một phương thức thì sẽ được cấp phát một vùng nhớ trong bộ nhớ stack theo luồng riêng. Khi hàm thực hiện xong thì khối bộ nhớ stack cho hàm sẽ bị xóa và giải phóng bộ nhớ trong stack.

Quy luật quản lý bộ nhớ trong stack: trong stack sử dụng quy luật LIFO (vào sau chết trước), nghĩa là: khối bộ nhớ được khởi tạo sau trong stack sẽ được giải phóng trước, khối bộ nhớ được khởi tạo trước trong stack sẽ được giải phóng sau.

Ví dụ minh họa

public class Car {
    public static void main(String[] args) { // Line 1
        int i = 1; // Line 2
        Object obj = new Object(); // Line 3
        Car car = new Car(); // Line 4
        car.driver(obj); // Line 5
    } // Line 6

    private void driver(Object object) { // Line 7
        String str = object.toString(); //// Line 8
        System.out.println(str);
    } // Line 9
}
Hình 1: sơ đồ minh họa quản lý bộ nhớ của đoạn code java phía trên

Giải thích ý nghĩa

  • Khi chạy chương trình thì một thread sẽ được khởi tạo và gọi hàm main() ở Line 1, lúc này một khối bộ nhớ cho hàm main() được khởi tạo trong stack.
  • Line 2 một biến cục bộ kiểu primitive được khởi tạo, lúc này nó sẽ được lưu trong cùng khối bộ nhớ của hàm main() trong stack.
  • Line 3 một đối tượng Object được khởi tạo, lúc này nó sẽ được lưu trong bộ nhớ heap, đồng thời biến tham chiếu của nó là obj được lưu vào khối bộ nhớ của hàm main() trong stack.
  • Line 4 một đối tượng Car được khởi tạo, lúc này nó sẽ được lưu trong bộ nhớ heap, đồng thời biến tham chiếu car của nó được lưu vào khối bộ nhớ của hàm main() trong stack.
  • Line 5 hàm driver() được gọi, lúc này một khối bộ nhớ cho hàm driver() được khởi tạo trong stack.
  • Line 7 hàm driver() có một đối số kiểu Object, lúc này biến tham chiếu object sẽ được lưu trong khối bộ nhớ của hàm driver() trong stack.
  • Line 8 một đối tượng cục bộ được khởi tạo, lúc này biến tham số str sẽ được lưu ở khối bộ nhớ hàm driver() và biến này tham chiếu tới đối tượng StringPool ở trong bộ nhớ Heap (chúng ta sẽ tìm hiểu StringPool ở các bài tiếp theo).
  • Line 9 thì hàm driver() kết thúc, lúc này khối bộ nhớ stack cho hàm driver() sẽ được giải phóng.
  • hàm main() kết thúc ở Line 6 nên khối bộ nhớ stack cho hàm main() cũng sẽ được giải phóng, vì nó tuân theo quy luật LIFO (vào sau chết trước) nên là khối bộ nhớ cho hàm driver() được khởi tạo sau sẽ giải phóng trước, sau đó đến hàm main() được khởi tạo trước nên giải phóng sau.

So sánh bộ nhớ Heap và Stack

Bộ nhớ HeapBộ nhớ Stack
Là bộ nhớ được sử dụng khi runtime, bất cứ khi nào đối tượng được khởi tạo trong chương trình thì nó sẽ được lưu trong bộ nhớ Heap (sử dụng toán tử new)Là bộ nhớ dùng để lưu các biến cục bộ trong hàm và lời gọi hàm ở runtime trong một Thread java. Biến cục bộ bao gồm các loại:
– biến loại nguyên thủy
– biến loại tham chiếu tới đối tượng lưu trong Heap
– biến loại đối số được truyền vào hàm
– biến loại được khởi tạo trong thân hàm
Thời gian sống phụ thuộc vào Garbage Collection, GC sẽ chạy trên bộ nhớ Heap để xoá các Object không được sử dụng nữa, nghĩa là object không được reference(tham chiếu) trong chương trình.Thời gian sống ngắn, sau khi phương thức kết thúc
các đối tượng lưu trong Heap được sử dụng bởi tất cả các nơi trong ứng dụng, bởi các thread khác nhaudữ liệu trong stack chỉ được sử dụng trong cùng một thread, các thread khác nhau không sử dụng dữ liệu của nhau
cơ chế quản lý của Heap phức tạp hơn stack, chia ra làm hai loại Young-Generation, Old-Generationcơ chế quản lý của stack là LIFO (Last in first out, vào sau ra trước)
Dung lượng Heap thường lớn hơn stackDung lượng stack thường nhỏ
Dung lượng sử dụng của Heap sẽ tăng giảm phụ thuộc vào số lượng đối tượng được tạo trong heap.Bất cứ khi nào gọi một hàm, thì một khối bộ nhớ stack cho hàm sẽ được khởi tạo, sau khi sử dụng xong thì khối bộ nhớ sẽ bị xóa, và giải phóng bộ nhớ cho stack
Sử dụng -Xms-Xmx để định nghĩa dung lượng bắt đầu và dung lượng tối đa của bộ nhớ heapDùng -Xss để định nghĩa dung lượng bộ nhớ stack.
Truy cập bộ nhớ Heap chậmTruy cập bộ nhớ Stack nhanh
Khi Heap bị đầy chương trình sẽ phát sinh lỗi java.lang.OutOfMemoryError: Java Heap SpaceKhi Stack bị đầy, chương trình phát sinh lỗi: java.lang.StackOverFlowError
So sánh giữa bộ nhớ Heap và bộ nhớ Stack

Xem thêm các bài viết liên quan dưới đây: