Quản lý bộ nhớ trong Java Câu hỏi phỏng vấn (+ Câu trả lời)

Bài viết này là một phần của loạt bài: • Câu hỏi phỏng vấn bộ sưu tập Java

• Câu hỏi phỏng vấn hệ thống kiểu Java

• Câu hỏi phỏng vấn đồng thời Java (+ câu trả lời)

• Câu hỏi phỏng vấn cấu trúc lớp và khởi tạo Java

• Câu hỏi phỏng vấn Java 8 (+ Câu trả lời)

• Quản lý bộ nhớ trong Java Câu hỏi phỏng vấn (+ Câu trả lời) (bài viết hiện tại) • Câu hỏi phỏng vấn Java Generics (+ Câu trả lời)

• Câu hỏi phỏng vấn kiểm soát luồng Java (+ Câu trả lời)

• Câu hỏi phỏng vấn ngoại lệ Java (+ câu trả lời)

• Câu hỏi phỏng vấn về chú thích Java (+ Câu trả lời)

• Câu hỏi phỏng vấn khung mùa xuân hàng đầu

1. Giới thiệu

Trong bài viết này, chúng ta sẽ khám phá một số câu hỏi quản lý bộ nhớ thường xuất hiện trong các cuộc phỏng vấn nhà phát triển Java. Quản lý bộ nhớ là một lĩnh vực mà không nhiều nhà phát triển quen thuộc.

Trên thực tế, các nhà phát triển thường không phải đối phó trực tiếp với khái niệm này - vì JVM sẽ chăm sóc các chi tiết thực tế. Trừ khi có điều gì đó sai nghiêm trọng, ngay cả những nhà phát triển dày dạn kinh nghiệm cũng có thể không có thông tin chính xác về quản lý bộ nhớ trong tầm tay của họ.

Mặt khác, những khái niệm này thực sự khá phổ biến trong các cuộc phỏng vấn - vì vậy chúng ta hãy bắt đầu ngay.

2. Câu hỏi

Q1. Câu lệnh “Bộ nhớ được quản lý trong Java” có nghĩa là gì?

Bộ nhớ là tài nguyên quan trọng mà một ứng dụng yêu cầu để chạy hiệu quả và giống như bất kỳ tài nguyên nào, nó rất khan hiếm. Do đó, việc phân bổ và phân bổ giao dịch của nó đến và đi từ các ứng dụng hoặc các phần khác nhau của một ứng dụng đòi hỏi rất nhiều sự cẩn thận và cân nhắc.

Tuy nhiên, trong Java, nhà phát triển không cần phải cấp phát và phân bổ bộ nhớ một cách rõ ràng - JVM và cụ thể hơn là Bộ thu gom rác - có nhiệm vụ xử lý cấp phát bộ nhớ để nhà phát triển không cần phải làm như vậy.

Điều này trái ngược với những gì xảy ra trong các ngôn ngữ như C, nơi một lập trình viên có quyền truy cập trực tiếp vào bộ nhớ và theo nghĩa đen là tham chiếu các ô nhớ trong mã của anh ta, tạo ra rất nhiều chỗ cho việc rò rỉ bộ nhớ.

Quý 2. Thu gom rác là gì và ưu điểm của nó là gì?

Thu gom rác là quá trình xem xét bộ nhớ heap, xác định đối tượng nào đang được sử dụng và đối tượng nào không, và xóa các đối tượng không sử dụng.

Một đối tượng đang sử dụng hoặc một đối tượng được tham chiếu có nghĩa là một số phần trong chương trình của bạn vẫn duy trì một con trỏ đến đối tượng đó. Một đối tượng không được sử dụng, hoặc đối tượng không được tham chiếu, không còn được tham chiếu bởi bất kỳ phần nào trong chương trình của bạn. Vì vậy, bộ nhớ được sử dụng bởi một đối tượng không được tham chiếu có thể được lấy lại.

Ưu điểm lớn nhất của thu gom rác là nó loại bỏ gánh nặng phân bổ / phân bổ bộ nhớ thủ công khỏi chúng tôi để chúng tôi có thể tập trung giải quyết vấn đề trong tầm tay.

Q3. Có bất kỳ nhược điểm nào của việc thu gom rác không?

Đúng. Bất cứ khi nào trình thu gom rác chạy, nó sẽ ảnh hưởng đến hiệu suất của ứng dụng. Điều này là do tất cả các luồng khác trong ứng dụng phải được dừng lại để cho phép luồng thu gom rác thực hiện hiệu quả công việc của nó.

Tùy thuộc vào yêu cầu của ứng dụng, đây có thể là một vấn đề thực sự mà khách hàng không thể chấp nhận được. Tuy nhiên, vấn đề này có thể được giảm thiểu đáng kể hoặc thậm chí loại bỏ thông qua tối ưu hóa khéo léo và điều chỉnh bộ thu gom rác và sử dụng các thuật toán GC khác nhau.

Q4. Ý nghĩa của thuật ngữ “Stop-The-World” là gì?

Khi luồng thu gom rác đang chạy, các luồng khác bị dừng, có nghĩa là ứng dụng bị dừng trong giây lát. Điều này tương tự như việc dọn dẹp nhà cửa hoặc xông hơi khử trùng nơi những người cư ngụ bị từ chối tiếp cận cho đến khi quá trình hoàn tất.

Tùy thuộc vào nhu cầu của một ứng dụng, việc thu gom rác “stop the world” có thể gây ra tình trạng đóng băng không thể chấp nhận được. Đây là lý do tại sao điều quan trọng là phải điều chỉnh bộ thu gom rác và tối ưu hóa JVM để việc đóng băng gặp phải là ít nhất có thể chấp nhận được.

Q5. Stack và Heap là gì? Những gì được lưu trữ trong mỗi cấu trúc bộ nhớ này, và chúng liên quan với nhau như thế nào?

Ngăn xếp là một phần của bộ nhớ chứa thông tin về các lệnh gọi phương thức lồng nhau xuống vị trí hiện tại trong chương trình. Nó cũng chứa tất cả các biến cục bộ và các tham chiếu đến các đối tượng trên heap được xác định trong các phương thức đang thực thi.

Cấu trúc này cho phép thời gian chạy trả về từ phương thức khi biết địa chỉ mà nó được gọi, và cũng xóa tất cả các biến cục bộ sau khi thoát khỏi phương thức. Mỗi luồng có ngăn xếp riêng của nó.

Heap là một phần lớn bộ nhớ dùng để phân bổ các đối tượng. Khi bạn tạo một đối tượng với từ khóa mới , nó sẽ được phân bổ trên heap. Tuy nhiên, tham chiếu đến đối tượng này nằm trên ngăn xếp.

Q6. Thu gom rác thế hệ là gì và điều gì khiến nó trở thành phương pháp thu gom rác phổ biến?

Thu gom rác theo thế hệ có thể được định nghĩa một cách lỏng lẻo là chiến lược được người thu gom rác sử dụng trong đó đống rác được chia thành một số phần được gọi là các thế hệ, mỗi phần sẽ chứa các đối tượng theo “tuổi” của chúng trên đống.

Bất cứ khi nào bộ thu gom rác đang chạy, bước đầu tiên của quy trình được gọi là đánh dấu. Đây là nơi bộ thu gom rác xác định phần bộ nhớ nào đang được sử dụng và phần nào không. Đây có thể là một quá trình rất tốn thời gian nếu tất cả các đối tượng trong một hệ thống phải được quét.

Khi càng nhiều đối tượng được phân bổ, danh sách đối tượng ngày càng lớn lên dẫn đến thời gian thu gom rác ngày càng lâu hơn. Tuy nhiên, phân tích thực nghiệm của các ứng dụng đã chỉ ra rằng hầu hết các đối tượng đều tồn tại trong thời gian ngắn.

Với việc thu gom rác theo thế hệ, các đối tượng được phân nhóm theo “tuổi” của chúng về số lượng rác đã tồn tại trong chu kỳ thu gom rác. Bằng cách này, phần lớn công việc trải dài trong các chu kỳ thu thập nhỏ và lớn khác nhau.

Ngày nay, hầu hết tất cả những người thu gom rác đều theo thế hệ. Chiến lược này rất phổ biến vì theo thời gian, nó đã được chứng minh là giải pháp tối ưu.

Q7. Mô tả chi tiết cách thức hoạt động của công việc thu gom rác thế hệ

Để hiểu đúng cách thức hoạt động của bộ sưu tập rác thế hệ, điều quan trọng trước tiên là phải nhớ cách heap Java được cấu trúc để tạo điều kiện thuận lợi cho việc thu gom rác thế hệ.

Heap được chia thành các không gian hoặc thế hệ nhỏ hơn. Những không gian này là Thế hệ trẻ, Thế hệ già hoặc Thế hệ có thời hạn, và Thế hệ vĩnh viễn.

Thế hệ trẻ lưu trữ hầu hết các đối tượng mới được tạo ra . Một nghiên cứu thực nghiệm về hầu hết các ứng dụng cho thấy rằng phần lớn các đối tượng có tuổi thọ nhanh chóng và do đó, sẽ sớm đủ điều kiện để thu thập. Do đó, các đối tượng mới bắt đầu cuộc hành trình của họ ở đây và chỉ được “thăng cấp” vào không gian thế hệ cũ sau khi chúng đã đạt được một “tuổi” nhất định.

Thuật ngữ “tuổi” trong thu gom rác thế hệ đề cập đến số chu kỳ thu gom mà đối tượng đã tồn tại .

Không gian thế hệ trẻ được chia thành ba không gian: một không gian Eden và hai không gian sống sót như Survivor 1 (s1) và Survivor 2 (s2).

Thế hệ cũ lưu trữ những đồ vật đã tồn tại trong trí nhớ lâu hơn một “tuổi” nhất định . Những đồ vật còn sót lại trong quá trình thu gom rác từ thế hệ trẻ được phát huy lên không gian này. Nó thường lớn hơn thế hệ trẻ. Vì kích thước lớn hơn nên việc thu gom rác tốn kém hơn và ít xảy ra hơn so với thế hệ trẻ.

Thế hệ vĩnh viễn hoặc thường được gọi là PermGen, chứa siêu dữ liệu theo yêu cầu của JVM để mô tả các lớp và phương thức được sử dụng trong ứng dụng. Nó cũng chứa nhóm chuỗi để lưu trữ các chuỗi xen kẽ. Nó được điền bởi JVM trong thời gian chạy dựa trên các lớp được ứng dụng sử dụng. Ngoài ra, các lớp và phương thức thư viện nền tảng có thể được lưu trữ tại đây.

Đầu tiên, bất kỳ đối tượng mới nào được phân bổ vào không gian Eden . Cả hai không gian sống sót đều bắt đầu trống rỗng. Khi không gian Eden đầy lên, một bộ sưu tập rác nhỏ sẽ được kích hoạt. Các đối tượng được tham chiếu được chuyển đến không gian sống sót đầu tiên. Các đối tượng không được tham chiếu sẽ bị xóa.

Trong GC nhỏ tiếp theo, điều tương tự cũng xảy ra với không gian Eden. Các đối tượng không được tham chiếu sẽ bị xóa và các đối tượng được tham chiếu được chuyển đến không gian sống sót. Tuy nhiên, trong trường hợp này, họ được chuyển đến không gian sống sót thứ hai (S2).

Ngoài ra, các vật thể từ GC nhỏ cuối cùng trong không gian sống sót đầu tiên (S1) có tuổi của chúng tăng dần và được chuyển đến S2. Khi tất cả các đối tượng sống sót đã được chuyển đến S2, cả S1 và không gian Eden đều bị xóa. Tại thời điểm này, S2 chứa các đối tượng có độ tuổi khác nhau.

Ở GC nhỏ tiếp theo, quy trình tương tự được lặp lại. Tuy nhiên lần này không gian của người sống sót chuyển đổi. Các đối tượng được tham chiếu được chuyển đến S1 từ cả Eden và S2. Đối tượng sống sót đã có tuổi. Eden và S2 được xóa.

Sau mỗi chu kỳ thu gom rác nhỏ, tuổi của từng đối tượng được kiểm tra. Những người đã đến một độ tuổi tùy ý nhất định, ví dụ, 8, được thăng cấp từ thế hệ trẻ sang thế hệ già hoặc đã trưởng thành. Đối với tất cả các chu kỳ GC nhỏ tiếp theo, các đối tượng sẽ tiếp tục được đưa vào không gian thế hệ cũ.

Điều này làm kiệt quệ quá trình thu gom rác thải ở thế hệ trẻ. Cuối cùng, một bộ sưu tập rác lớn sẽ được thực hiện trên thế hệ cũ để dọn dẹp và thu gọn không gian đó. Đối với mỗi Tổng công ty chính, có một số Tổng công ty nhỏ.

Q8. Khi nào thì một đối tượng trở nên đủ điều kiện để thu gom rác? Mô tả Cách Gc Thu thập Đối tượng Đủ điều kiện?

Một đối tượng đủ điều kiện để thu gom rác hoặc GC nếu nó không thể truy cập được từ bất kỳ chuỗi trực tiếp nào hoặc bằng bất kỳ tham chiếu tĩnh nào.

Trường hợp đơn giản nhất của một đối tượng trở nên đủ điều kiện để thu gom rác là nếu tất cả các tham chiếu của nó là rỗng. Các phần phụ thuộc theo chu kỳ mà không có bất kỳ tham chiếu bên ngoài trực tiếp nào cũng đủ điều kiện cho GC. Vì vậy, nếu đối tượng A tham chiếu đến đối tượng B và đối tượng B tham chiếu đến đối tượng A và chúng không có bất kỳ tham chiếu trực tiếp nào khác thì cả đối tượng A và B sẽ đủ điều kiện để thu gom rác.

Một trường hợp rõ ràng khác là khi một đối tượng cha được đặt thành null. Khi một đối tượng nhà bếp tham chiếu nội bộ đối tượng tủ lạnh và đối tượng bồn rửa và đối tượng bếp được đặt thành null, cả tủ lạnh và bồn rửa sẽ đủ điều kiện để thu gom rác cùng với nhà bếp mẹ của chúng.

Q9. Làm thế nào để bạn kích hoạt Bộ sưu tập rác từ Mã Java?

Bạn, với tư cách là lập trình viên Java, không thể buộc thu thập rác trong Java ; nó sẽ chỉ kích hoạt nếu JVM cho rằng nó cần một bộ sưu tập rác dựa trên kích thước đống Java.

Trước khi xóa một đối tượng khỏi chuỗi thu gom rác bộ nhớ sẽ gọi phương thức finalize () của đối tượng đó và tạo cơ hội để thực hiện bất kỳ loại dọn dẹp nào được yêu cầu. Bạn cũng có thể gọi phương thức này của một mã đối tượng, tuy nhiên, không có gì đảm bảo rằng việc thu gom rác sẽ xảy ra khi bạn gọi phương thức này.

Ngoài ra, có các phương thức như System.gc () và Runtime.gc () được sử dụng để gửi yêu cầu thu gom rác tới JVM nhưng không đảm bảo rằng việc thu gom rác sẽ diễn ra.

Q. 10. Điều gì sẽ xảy ra khi không có đủ không gian đống để chứa các đối tượng mới?

Nếu không có không gian bộ nhớ để tạo một đối tượng mới trong Heap, Máy ảo Java ném OutOfMemoryError hoặc cụ thể hơn là không gian heap java.lang.OutOfMemoryError .

Q11. Có thể «Phục hồi» Vật thể Đủ điều kiện để Thu gom Rác không?

Khi một đối tượng đủ điều kiện để thu gom rác, GC phải chạy phương thức finalize trên nó. Các Finalize phương pháp được đảm bảo để chạy một lần duy nhất, do đó những lá cờ GC đối tượng đã được hoàn chỉnh và cung cấp cho nó nghỉ ngơi cho đến khi chu kỳ tiếp theo.

Trong phương thức finalize, về mặt kỹ thuật, bạn có thể “hồi sinh” một đối tượng, chẳng hạn bằng cách gán nó vào một trường tĩnh . Đối tượng sẽ trở nên sống động trở lại và không đủ điều kiện để thu gom rác, vì vậy GC sẽ không thu thập nó trong chu kỳ tiếp theo.

Tuy nhiên, đối tượng sẽ được đánh dấu là đã hoàn tất, vì vậy khi nó đủ điều kiện trở lại, phương thức finalize sẽ không được gọi. Về bản chất, bạn có thể biến thủ thuật “hồi sinh” này chỉ một lần trong suốt thời gian tồn tại của đối tượng. Hãy lưu ý rằng thủ thuật xấu xí này chỉ nên được sử dụng nếu bạn thực sự biết mình đang làm gì - tuy nhiên, hiểu được thủ thuật này sẽ giúp bạn hiểu rõ hơn về cách thức hoạt động của GC.

Q12. Mô tả các tham chiếu Mạnh, Yếu, Mềm và Ma và Vai trò của Chúng trong Thu gom Rác.

Phần lớn bộ nhớ được quản lý bằng Java, một kỹ sư có thể cần thực hiện tối ưu hóa nhiều nhất có thể để giảm thiểu độ trễ và tối đa hóa thông lượng, trong các ứng dụng quan trọng. Nhiều khi không thể kiểm soát rõ ràng thời điểm kích hoạt thu gom rác trong JVM, nên có thể ảnh hưởng đến cách nó xảy ra liên quan đến các đối tượng mà chúng tôi đã tạo.

Java cung cấp cho chúng ta các đối tượng tham chiếu để kiểm soát mối quan hệ giữa các đối tượng chúng ta tạo và bộ thu gom rác.

Theo mặc định, mọi đối tượng chúng tôi tạo trong chương trình Java đều được tham chiếu mạnh mẽ bởi một biến:

StringBuilder sb = new StringBuilder();

Trong đoạn mã trên, từ khóa mới tạo một đối tượng StringBuilder mới và lưu trữ nó trên heap. Sau đó biến sb lưu trữ một tham chiếu mạnh đến đối tượng này. Điều này có nghĩa là gì đối với bộ thu gom rác là đối tượng StringBuilder cụ thể không đủ điều kiện để thu thập do một tham chiếu mạnh được giữ bởi sb . Câu chuyện chỉ thay đổi khi chúng ta vô hiệu hóa sb như thế này:

sb = null;

Sau khi gọi đến đường dây trên, đối tượng sau đó sẽ đủ điều kiện thu tiền.

Chúng ta có thể thay đổi mối quan hệ này giữa đối tượng và bộ thu gom rác bằng cách gói rõ ràng nó bên trong một đối tượng tham chiếu khác nằm bên trong gói java.lang.ref .

Một tham chiếu mềm có thể được tạo cho đối tượng trên như thế này:

StringBuilder sb = new StringBuilder(); SoftReference sbRef = new SoftReference(sb); sb = null;

Trong đoạn mã trên, chúng tôi đã tạo hai tham chiếu đến đối tượng StringBuilder . Dòng đầu tiên tạo ra một tham chiếu mạnh sb và dòng thứ hai tạo một tham chiếu mềm sbRef . Dòng thứ ba sẽ làm cho đối tượng đủ điều kiện để thu thập nhưng trình thu gom rác sẽ hoãn việc thu thập nó vì sbRef .

Câu chuyện sẽ chỉ thay đổi khi bộ nhớ trở nên eo hẹp và JVM đang trên bờ vực mắc lỗi OutOfMemory . Nói cách khác, các đối tượng chỉ có tham chiếu mềm được thu thập như một biện pháp cuối cùng để khôi phục bộ nhớ.

Một tham chiếu yếu có thể được tạo theo cách tương tự bằng cách sử dụng lớp WeakReference . Khi sb được đặt thành null và đối tượng StringBuilder chỉ có một tham chiếu yếu, trình thu gom rác của JVM sẽ hoàn toàn không có sự thỏa hiệp và ngay lập tức thu thập đối tượng vào chu kỳ tiếp theo.

Một tham chiếu ảo tương tự như một tham chiếu yếu và một đối tượng chỉ có tham chiếu ảo sẽ được thu thập mà không cần chờ đợi. Tuy nhiên, các tham chiếu ma được xếp vào hàng ngay sau khi các đối tượng của chúng được thu thập. Chúng ta có thể thăm dò hàng đợi tham chiếu để biết chính xác thời điểm đối tượng được thu thập.

Q13. Giả sử chúng ta có một tham chiếu tròn (Hai đối tượng tham chiếu nhau). Những Cặp Đối Tượng Như Thế Có Thể Đủ Điều Kiện Để Thu Rác Không Và Tại Sao?

Có, một cặp đối tượng có tham chiếu hình tròn có thể đủ điều kiện để thu gom rác. Điều này là do cách trình thu gom rác của Java xử lý các tham chiếu vòng tròn. Nó coi các đối tượng đang tồn tại không phải khi chúng có bất kỳ tham chiếu nào đến chúng, mà là khi chúng có thể truy cập được bằng cách điều hướng biểu đồ đối tượng bắt đầu từ gốc thu gom rác nào đó (biến cục bộ của luồng trực tiếp hoặc trường tĩnh). Nếu một cặp đối tượng có tham chiếu vòng không thể truy cập được từ bất kỳ gốc nào, thì nó được coi là đủ điều kiện để thu gom rác.

Q14. Các chuỗi được biểu diễn như thế nào trong bộ nhớ?

Một cá thể Chuỗi trong Java là một đối tượng có hai trường: trường giá trị char [] và trường băm int . Các giá trị lĩnh vực là một mảng ký tự đại diện cho chuỗi chính nó, và băm lĩnh vực chứa hashCode của một chuỗi mà được khởi tạo với zero, tính trong sáu tháng đầu hashCode () cuộc gọi và lưu trữ kể từ đó. Như một trường hợp cạnh kỳ lạ, nếu Mã băm của một chuỗi có giá trị bằng 0, thì nó phải được tính toán lại mỗi khi Mã băm () được gọi.

Điều quan trọng là một cá thể Chuỗi là bất biến: bạn không thể lấy hoặc sửa đổi mảng char [] bên dưới . Một tính năng khác của chuỗi là các chuỗi hằng số tĩnh được tải và lưu vào bộ nhớ cache trong một nhóm chuỗi. Nếu bạn có nhiều đối tượng Chuỗi giống hệt nhau trong mã nguồn của mình, tất cả chúng đều được thể hiện bằng một phiên bản duy nhất trong thời gian chạy.

Q15. Stringbuilder là gì và các trường hợp sử dụng của nó là gì? Sự khác biệt giữa việc nối một chuỗi vào một trình xây dựng chuỗi và nối hai chuỗi với một toán tử + là gì? Stringbuilder khác với Stringbuffer như thế nào?

StringBuilder cho phép thao tác các chuỗi ký tự bằng cách thêm, xóa và chèn các ký tự và chuỗi. Đây là một cấu trúc dữ liệu có thể thay đổi, trái ngược với lớp String là bất biến.

Khi nối hai cá thể Chuỗi , một đối tượng mới sẽ được tạo và các chuỗi được sao chép. Điều này có thể mang lại chi phí thu gom rác khổng lồ nếu chúng ta cần tạo hoặc sửa đổi một chuỗi trong vòng lặp. StringBuilder cho phép xử lý các thao tác chuỗi hiệu quả hơn nhiều.

StringBuffer khác với StringBuilder ở chỗ nó an toàn theo luồng. Nếu bạn cần thao tác một chuỗi trong một chuỗi, hãy sử dụng StringBuilder để thay thế.

3. Kết luận

Trong bài viết này, chúng tôi đã đề cập đến một số câu hỏi phổ biến nhất thường xuất hiện trong các cuộc phỏng vấn kỹ sư Java. Các câu hỏi về quản lý bộ nhớ chủ yếu được hỏi đối với các ứng viên Nhà phát triển Java cao cấp vì người phỏng vấn kỳ vọng rằng bạn đã xây dựng các ứng dụng không tầm thường mà rất nhiều lần bị cản trở bởi các vấn đề về bộ nhớ.

Đây không nên được coi là một danh sách đầy đủ các câu hỏi, mà là một bệ phóng để nghiên cứu thêm. Tại Baeldung, chúng tôi chúc bạn thành công trong những cuộc phỏng vấn sắp tới.

Tiếp theo » Câu hỏi phỏng vấn Java Generics (+ Câu trả lời) « Câu hỏi phỏng vấn Java 8 trước (+ Câu trả lời)