So sánh được bộ nhớ stack và heap năm 2024

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ý.

Mục Lục

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.

Biến địa phương, trong đó có các tham số, được khai báo bên trong một phương thức. Chúng là các biến tạm thời, chúng sống bên trong khung bộ nhớ của phương thức và chỉ tồn tại khi phương thức còn nằm trong bộ nhớ stack, nghĩa là khi phương thức đang chạy và chưa chạy đến ngoặc kết thúc (})

Vậy còn các biến địa phương là các đối tượng? Nhớ lại rằng trong Java một biến thuộc kiểu không cơ bản thực ra là một tham chiếu tới một đối tượng chứ không phải chính đối tượng đó. Do đó, biến địa phương đó vẫn nằm trong stack, còn đối tượng mà nó chiếu tới vẫn nằm trong heap. Bất kể tham chiếu được khai báo ở đâu, là biến địa phương của một phương thức hay là biến thực thể của một lớp, đối tượng mà nó chiếu tới bao giờ cũng nằm trong heap.

Vậy biến thực thể nằm ở đâu? Các biến thực thể đi kèm theo từng đối tượng, chúng sống bên trong vùng bộ nhớ của đối tượng chủ tại heap. Mỗi khi ta gọi new Cow(), Java cấp phát bộ nhớ cho đối tượng Cow đó tại heap, lượng bộ nhớ được cấp phát đủ chỗ để lưu giá trị của tất cả các biến thực thể của đối tượng đó. Nếu biến thực thể thuộc kiểu cơ bản, vùng bộ nhớ được cấp phát cho nó có kích thước tùy theo kích thước của kiểu dữ liệu nó được khai báo. Ví dụ một biến int cần 32 bit. Còn nếu biến thực thể là đối tượng thì sao? Chẳng hạn, Car HAS-A Engine (ô tô có một động cơ), nghĩa là mỗi đối tượng Car có một biến thực thể là tham chiếu kiểu Engine. Java cấp phát bộ nhớ bên trong đối tượng Car đủ để lưu biến tham chiếu engine. Còn bản thân biến này sẽ chiếu tới một đối tượng Engine nằm bên ngoài, chứ không phải bên trong, đối tượng Car.

Vậy khi nào đối tượng Engine được cấp phát bộ nhớ trong heap? Khi nào lệnh new Engine() cho nó được chạy. Chẳng hạn, trong ví dụ hình bên dưới, đối tượng Engine được tạo mới để khởi tạo giá trị cho biến thực thể engine, lệnh khởi tạo nằm ngay trong khai báo lớp Car.

Còn trong ví dụ bên dưới, không có đối tượng Engine nào được tạo khi đối tượng Car được cấp phát bộ nhớ, engine không được khởi tạo. Ta sẽ cần đến các lệnh riêng biệt ở sau đó để tạo đối tượng Engine và gán trị cho engine, chẳng hạn như c.engine = new Engine();

Ngăn xếp là một vùng đặc biệt của bộ nhớ máy tính, nơi lưu trữ các biến tạm thời được tạo bởi một hàm. Trong ngăn xếp, các biến được khai báo, lưu trữ và khởi tạo trong thời gian chạy.

Đó là bộ nhớ lưu trữ tạm thời. Khi tác vụ tính toán hoàn tất, bộ nhớ của biến sẽ tự động bị xóa. Phần ngăn xếp chủ yếu chứa các phương thức, biến cục bộ và biến tham chiếu.

Đống là gì?

Heap là bộ nhớ được các ngôn ngữ lập trình sử dụng để lưu trữ các biến toàn cục. Theo mặc định, tất cả các biến toàn cục được lưu trữ trong không gian bộ nhớ heap. Nó hỗ trợ phân bổ bộ nhớ động.

Vùng heap không được quản lý tự động cho bạn và không được CPU quản lý chặt chẽ. Nó giống một vùng ký ức trôi nổi tự do hơn.

Sự khác biệt chính giữa Stack và Heap

So sánh được bộ nhớ stack và heap năm 2024

Tham số Sắp xếp ban ơn Kiểu cấu trúc dữ liệu Ngăn xếp là một cấu trúc dữ liệu tuyến tính. Heap là một hierarchicấu trúc dữ liệu cal. Tốc độ truy cập Truy cập tốc độ cao Chậm hơn so với ngăn xếp Quản lý không gian Không gian được hệ điều hành quản lý hiệu quả nên bộ nhớ sẽ không bao giờ bị phân mảnh. Không gian Heap không được sử dụng hiệu quả Bộ nhớ có thể bị phân mảnh khi các khối bộ nhớ được phân bổ lần đầu tiên và sau đó được giải phóng. Truy Cập Chỉ các biến cục bộ Nó cho phép bạn truy cập các biến trên toàn cầu. Giới hạn kích thước không gian Giới hạn kích thước ngăn xếp tùy thuộc vào hệ điều hành. Không có giới hạn cụ thể về kích thước bộ nhớ. Thay đổi kích thước Các biến không thể thay đổi kích thước Các biến có thể được thay đổi kích thước. Cấp phát bộ nhớ Bộ nhớ được phân bổ trong một khối liền kề. Bộ nhớ được phân bổ theo thứ tự ngẫu nhiên. Phân bổ và phân bổ Tự động thực hiện theo hướng dẫn của trình biên dịch. Nó được thực hiện thủ công bởi lập trình viên. Phân bổ Không yêu cầu phân bổ lại các biến. Cần phải phân bổ lại rõ ràng. Phí Tổn Ít hơn Hơn Thực hiện Một ngăn xếp có thể được triển khai theo 3 cách dựa trên mảng đơn giản, sử dụng bộ nhớ động và dựa trên danh sách liên kết. Heap có thể được thực hiện bằng cách sử dụng mảng và cây. Vấn đề chính Thiếu bộ nhớ Phân mảnh bộ nhớ Địa phương tham khảo Hướng dẫn thời gian biên dịch tự động. Đầy đủ Linh hoạt Kích thước cố định Có thể thay đổi kích thước Thời gian truy cập Nhanh hơn Chậm hơn

Ưu điểm của việc sử dụng Stack

So sánh được bộ nhớ stack và heap năm 2024

Dưới đây là những ưu/lợi ích của việc sử dụng ngăn xếp:

  • Giúp bạn quản lý dữ liệu theo phương pháp Vào trước ra trước (LIFO) mà danh sách và mảng được liên kết không thể thực hiện được.
  • Khi một hàm được gọi, các biến cục bộ được lưu trữ trong một ngăn xếp và nó sẽ tự động bị hủy sau khi được trả về.
  • Ngăn xếp được sử dụng khi một biến không được sử dụng bên ngoài hàm đó.
  • Nó cho phép bạn kiểm soát cách phân bổ và giải phóng bộ nhớ.
  • Stack tự động dọn sạch đối tượng.
  • Không dễ bị hỏng
  • Các biến không thể thay đổi kích thước.

Ưu điểm của việc sử dụng Heap

So sánh được bộ nhớ stack và heap năm 2024

Ưu điểm/lợi ích của việc sử dụng bộ nhớ heap là:

  • Heap giúp bạn tìm số lớn nhất và số nhỏ nhất
  • Bộ sưu tập rác chạy trên bộ nhớ heap để giải phóng bộ nhớ được đối tượng sử dụng.
  • Phương pháp heap cũng được sử dụng trong Hàng đợi ưu tiên.
  • Nó cho phép bạn truy cập các biến trên toàn cầu.
  • Heap không có bất kỳ giới hạn nào về kích thước bộ nhớ.

Nhược điểm của việc sử dụng Stack

Nhược điểm/Hạn chế của việc sử dụng bộ nhớ Stack là:

  • Bộ nhớ ngăn xếp rất hạn chế.
  • Tạo quá nhiều đối tượng trên ngăn xếp có thể làm tăng nguy cơ tràn ngăn xếp.
  • Truy cập ngẫu nhiên là không thể.
  • Bộ nhớ biến sẽ bị ghi đè, điều này đôi khi dẫn đến hành vi không xác định của chức năng hoặc chương trình.
  • Ngăn xếp sẽ rơi ra ngoài vùng bộ nhớ, điều này có thể dẫn đến việc chấm dứt bất thường.

Nhược điểm của việc sử dụng Heap

Nhược điểm/nhược điểm của việc sử dụng bộ nhớ Heaps là:

  • Nó có thể cung cấp bộ nhớ tối đa mà hệ điều hành có thể cung cấp
  • Phải mất nhiều thời gian hơn để tính toán.
  • Việc quản lý bộ nhớ phức tạp hơn trong bộ nhớ heap vì nó được sử dụng trên toàn cầu.
  • Mất quá nhiều thời gian thực hiện so với ngăn xếp.

Khi nào nên sử dụng Heap hoặc stack?

Bạn nên sử dụng heap khi cần phân bổ một khối bộ nhớ lớn. Ví dụ: bạn muốn tạo một mảng có kích thước lớn hoặc cấu trúc lớn để giữ biến đó trong thời gian dài thì bạn nên phân bổ nó vào heap.

Tuy nhiên, nếu bạn đang làm việc với các biến tương đối nhỏ chỉ được yêu cầu cho đến khi hàm sử dụng chúng còn hoạt động. Sau đó, bạn cần sử dụng ngăn xếp, cách này nhanh hơn và dễ dàng hơn.