Stack Memory và Heap Space trong Java
Người dịch: Trần Ngọc Quân - Học viên lớp Java08
Email liên hệ: quan.tranngoc317@gmail.com
Bài viết gốc: https://www.baeldung.com/java-stack-heap
1. Mở đầu
Để chạy một chương trình theo cách tối ưu nhất, JVM chia bộ nhớ ra thành stack memory và heap memory. Mỗi khi ta khai báo các biến (variable) hoặc đối tượng (object) mới, gọi một phương thức mới, khai báo một String hoặc thực hiện những operation tương tự, JVM sẽ chỉ định bộ nhớ cho các operation đó cho Stack Memory hoặc Heap Space.
Trong bài viết này, ta sẽ nghiên cứu về những kiểu bộ nhớ (memory) trên. Đầu tiên, ta sẽ khám phá những đặc điểm chính của chúng. Sau đó, ta sẽ tìm hiểu cách chúng được lưu trữ trong RAM như thế nào và sử dụng chúng ở đâu. Cuối cùng, ta sẽ thảo luận những điểm khác biệt chính giữa hai loại bộ nhớ.
2. Stack Memory trong Java
Stack Memory trong java được sử dụng cho thao tác cấp phát bộ nhớ tĩnh và thao tác triển khai của 1 luồng – thread. Nó chứa các giá trị primitive cụ thể ứng với một phương thức hoặc tham chiếu đến các đối tượng lưu trong heap mà được gọi đến trong phương thức đó.
Dòng truy cập vào bộ nhớ Stack theo trình tự Last-In-First-Out (LIFO). Mỗi khi ta gọi một phương thức mới, 1 ô nhớ mới sẽ được khởi tạo trên đỉnh của stack và chứa các giá trị riêng biệt cho phương thức đó như các biến kiểu tham trị (primitive) và các biến tham chiếu (reference) đến các đối tượng (object).
Sau khi phương thức được triển khai xong, phần ô nhớ tương ứng với nó sẽ được giải phóng, flow của chương trình sẽ quay trở lại thao tác gọi phương thức và phương thức tiếp theo sẽ được gọi.
2.1. Đặc điểm chính của Stack Memory
Một số đặc trưng của stack memory bao gồm:
• Nó có thể phát triển hoặc thu nhỏ lần lượt khi các phương thức mới được gọi và return.
• Các biến trong stack chỉ tồn tại khi phương thức chứa chúng đang chạy
• Stack memory được tự động cấp phát và giải phóng khi phương thức chạy và kết thúc
• Nếu bộ nhớ stack đầy, lỗi java.lang.StackOverFlowError sẽ xuất hiện
• Truy cập vào stack memory nhanh hơn so với heap memory
• Bộ nhớ stack là threadsafe, do mỗi thread sẽ chạy trong stack riêng của chính nó.
3. Heap Space trong Java
Heap space được sử dụng trong thao tác cấp phát bộ nhớ động của các đối tượng Java và các lớp (class) JRE trong giai đoạn runtime. Các đối tượng mới luôn luôn được tạo ra trong heap space trong khi các tham chiếu đến những đối tượng đó sẽ được lưu trong stack memory.
Những đối tượng này được truy cập trong phạm vi toàn cục do đó ta có thể truy cập chúng từ bất cứ nơi nào trong ứng dụng
Ta có thể chia Heap space thành các phần nhỏ gọi là generations bao gồm:
- Young Generation – đây là nơi tất cả các đối tượng mới được lưu trữ và tồn tại. Garbage collection sẽ xảy ra khi phần bộ nhớ này bị đầy.
- Old or Tenured Generation – Đây là nơi những object tồn tại thời gian dài được lưu trữ. Khi các object lưu trữ trong Young Generation, một ngưỡng thời gian nhất định sẽ được quy định, khi thời gian tồn tại vượt quá ngưỡng này, object sẽ được chuyển sang old generation.
- Permanent Generation – Chứa JVM metadata của các lớp (class) và phương thức (method) của ứng dụng
3.1. Đặc điểm chính của Heap Memory
Một số đặc trưng của heap space bao gồm:
- Được truy cập thông qua các kĩ thuật quản lý bộ nhớ phức tạp bao gồm Young Generation, Old hoặc Tenured Generation, và Permanent Generation
- Nếu heap space bị đầy, sẽ xuất hiện lỗi java.lang.OutOfMemoryError.
- Truy cập heap space chậm hơn so với stack memory
- Heap space sẽ không được giải phóng tự động. Nó cần Garbage Collector dọn dẹp những object không còn được sử dụng để tăng hiệu năng của bộ nhớ.
- Không như stack, một heap space không threadsafe và cần được bảo vệ bằng cách đồng bộ code
4. Ví dụ
Dựa trên những kiến thức vừa tìm hiểu, cùng xem 1 ví dụ đơn giản để đánh giá cách quản lý bộ nhớ:
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
}
public class PersonBuilder {
private static Person buildPerson(int id, String name) {
return new Person(id, name);
}
public static void main(String[] args) {
int id = 23;
String name = "John";
Person person = null;
person = buildPerson(id, name);
}
}
Hãy phân tích ví dụ trên từng bước một:
- Khi ta sử dụng hàm main(), một ô nhớ trong stack memory sẽ được tạo ra để lưu các biến tham trị và tham chiếu của hàm này.
- Stack memory trực tiếp lưu trữ giá trị nguyên thủy của integer id.
- Biến tham chiếu person của class Person cũng sẽ được tạo ra trong stack memory và trỏ đến đối tượng thực trong heap space.
- Khi gọi constructor Person(int,String) trong hàm main(), một ô nhớ sẽ được cấp phát trên đỉnh của stack trước đó. Nó sẽ lưu trữ:
- Đối tượng tham chiếu this của đối tượng được gọi trong stack memory.Giá trị nguyên thủy id trong stack memory.
- Biến tham chiếu kiểu String của đối số name trỏ đến 1 string thực trong string pool của heap memory.
- Hàm main sau đó sẽ gọi phương thức tĩnh buildPerson(), và tiếp tục 1 ô nhớ trong stack memory sẽ được tạo trên đỉnh của stack trước đó. Nó sẽ lại một lần nữa lưu các biến theo cách tương tự ở bước 2.
- Tuy nhiên, heap memory sẽ lưu trữ tất cả các giá trị biến của đối tượng person mới được khởi tạo
Hãy xem minh họa cách chỉ định bộ nhớ theo sơ đồ bên dưới:
5. Tổng kết
Trước khi kết thức bài viết, hãy cùng tổng kết lại các điểm khác nhau giữa Stack Memory và Heap Space:
Đặc điểm | Stack Memory | Heap Space |
---|---|---|
Ứng dụng | Stack được sử dụng theo các phần, mỗi phần được sử dụng tại 1 thời điểm ứng với quá trình triển khai của 1 thread | Toàn bộ ứng dụng sử dụng Heap space trong quá trình runtime |
Kích thước | Stack có giới hạn dung lượng tùy thuộc vào hệ điều hành và thường sẽ nhỏ hơn Heap | Không có giới hạn về dung lượng bộ nhớ |
Lưu trữ | Chỉ lưu trữ các biến tham trị và biến tham chiếu trỏ đến các đối tượng được tạo ra trong Heap Space | Tất cả các đối tượng được tạo mới đều được lưu trữ ở đây |
Trình tự | Truy cập theo cơ chế Last-in First-out (LIFO) | Truy cập thông qua các kĩ thuật quản lý bộ nhớ phức tạp bao gồm Young Generation, Old hoặc Tenured Generation, và Permanent Generation |
Vòng đời | Stack memory chỉ tồn tại trong quá trình phương thức đang chạy | Heap space tồn tại trong quá trình ứng dụng chạy |
Hiệu quả | cấp phát bộ nhớ nhanh hơn Heap | cấp phát bộ nhớ chậm hơn stack |
Cấp phát/giải phóng | được cấp phát và giải phóng tự động lần lượt khi phương thức được gọi và kết thúc | Heap space được cấp phát khi object mới được tạo và được dọn dẹp bởi Garbage Collector khi không còn được sử dụng nữa |
6. Kết luận
Stack và heap là 2 cách để Java cấp phát bộ nhớ. Trong bài viết này, ta đã tìm hiểu cách chúng vận hành và khi nào nên sử dụng chúng để tối ưu ứng dụng Java.
Bình luận