Làm thế nào để giải phóng bộ nhớ trong Java?


146

Có cách nào để giải phóng bộ nhớ trong Java, tương tự như free()chức năng của C không? Hoặc là thiết lập đối tượng thành null và dựa vào tùy chọn duy nhất?


151
Ok ... hãy nói thẳng một điều. Chỉ vì bạn nghĩ rằng một cái gì đó là thực tiễn xấu và không phải là thứ để khuyến khích làm, không làm cho nó xứng đáng để bỏ phiếu. Đây là một câu hỏi rõ ràng và hợp lệ, hỏi xem có cách nào để giải phóng bộ nhớ trong Java mà không phụ thuộc vào bộ sưu tập rác. Mặc dù nó có thể không được khuyến khích và thường không hữu ích hoặc là một ý tưởng tốt, bạn không thể biết rằng không có kịch bản nào có thể được yêu cầu mà không biết những gì Felix biết. Felix thậm chí có thể không có kế hoạch sử dụng nó. Anh ta có thể chỉ muốn biết nếu nó có thể. Nó, không có cách nào, xứng đáng bỏ phiếu xuống.
Daniel Bingham

7
Để làm rõ, điều đó nhằm vào bất cứ ai đã bỏ phiếu này - không nhất thiết phải bình luận trước đó.
Daniel Bingham

Câu trả lời:


96

Java sử dụng bộ nhớ được quản lý, vì vậy cách duy nhất bạn có thể phân bổ bộ nhớ là sử dụng newtoán tử và cách duy nhất bạn có thể phân bổ bộ nhớ là dựa vào trình thu gom rác.

Bản trắng quản lý bộ nhớ (PDF) này có thể giúp giải thích những gì đang diễn ra.

Bạn cũng có thể gọi System.gc()để đề xuất rằng trình thu gom rác chạy ngay lập tức. Tuy nhiên, Java Runtime đưa ra quyết định cuối cùng, không phải mã của bạn.

Theo tài liệu Java ,

Gọi phương thức gc cho thấy Máy ảo Java dành nỗ lực tái chế các đối tượng không sử dụng để làm cho bộ nhớ mà chúng hiện đang chiếm dụng có sẵn để sử dụng lại nhanh chóng. Khi điều khiển trả về từ lệnh gọi phương thức, Máy ảo Java đã nỗ lực hết sức để lấy lại không gian từ tất cả các đối tượng bị loại bỏ.


5
Nó không buộc Garbage Collector chạy. Nó không buộc nó phải giải phóng bộ nhớ mặc dù ...
Pablo Santa Cruz

13
Không Pablo, nó không buộc GC phải chạy.
Jesper

1
Tôi đã được một người rất đáng tin cậy nói rằng tất cả những người thu gom rác của HotSpotVM System.gc()hoàn toàn bỏ qua .
Esko

1
Trên winXp java SE GC chạy mọi System.gc () hoặc gần như mọi nhưng tài liệu API không đảm bảo điều đó.
teodozjan

2
@Pablo Santa Cruz Bạn có nghĩa là nó không có bộ nhớ trống? Tôi vừa thử nghiệm nó trên chương trình của tôi dường như có một rò rỉ và việc sử dụng ram dường như ổn định? Và Daniel chỉ nói rằng nó chỉ gợi ý cho nó sau đó làm thế nào mà tỷ lệ ram được sử dụng luôn ổn định mỗi khi tôi gọi phương thức này. Bạn đang làm tôi bối rối.

65

Dường như không ai đề cập đến việc thiết lập rõ ràng các tham chiếu đối tượng null, đây là một kỹ thuật hợp pháp để "giải phóng" bộ nhớ mà bạn có thể muốn xem xét.

Ví dụ: giả sử bạn đã khai báo một List<String>phần đầu của một phương thức có kích thước rất lớn, nhưng chỉ được yêu cầu cho đến khi đi được một nửa phương thức. Tại thời điểm này, bạn có thể đặt tham chiếu Danh sách nullđể cho phép trình thu gom rác có khả năng lấy lại đối tượng này trước khi phương thức hoàn thành (và dù sao tham chiếu cũng nằm ngoài phạm vi).

Lưu ý rằng tôi hiếm khi sử dụng kỹ thuật này trong thực tế nhưng nó đáng để xem xét khi xử lý các cấu trúc dữ liệu rất lớn.


8
Nếu bạn thực sự đang làm rất nhiều công việc trên một đối tượng chỉ được sử dụng cho một phần của phương pháp tôi đề nghị; phương pháp của bạn quá phức tạp, chia phương thức thành các phần trước và sau hoặc sử dụng một khối cho nửa mã đầu tiên (phần sau hữu ích hơn cho các tập lệnh kiểm tra)
Peter Lawrey

5
Nơi đặt tham chiếu đối tượng thành null là quan trọng khi nó được tham chiếu từ một đối tượng tồn tại lâu dài khác (hoặc có thể từ một var tĩnh). Ví dụ, nếu bạn có một mảng các đối tượng lớn tồn tại lâu và bạn ngừng sử dụng một trong những đối tượng đó, bạn nên đặt tham chiếu mảng thành null để làm cho đối tượng có sẵn cho GC.
Licks nóng

22
System.gc(); 

Chạy bộ thu gom rác.

Gọi phương thức gc cho thấy Máy ảo Java dành nỗ lực tái chế các đối tượng không sử dụng để làm cho bộ nhớ mà chúng hiện đang chiếm dụng có sẵn để sử dụng lại nhanh chóng. Khi điều khiển trả về từ lệnh gọi phương thức, Máy ảo Java đã nỗ lực hết sức để lấy lại không gian từ tất cả các đối tượng bị loại bỏ.

Không được khuyến khích.

Chỉnh sửa: Tôi đã viết phản hồi ban đầu vào năm 2009. Bây giờ là 2015.

Những người thu gom rác đã dần ổn định hơn trong khoảng 20 năm Java tồn tại. Tại thời điểm này, nếu bạn gọi thủ công trình thu gom rác, bạn có thể muốn xem xét các phương pháp khác:

  • Nếu bạn đang buộc GC trên một số lượng máy hạn chế, có thể đáng để có một bộ cân bằng tải cách xa máy hiện tại, chờ cho nó kết thúc phục vụ cho các máy khách được kết nối, hết thời gian chờ kết nối, và sau đó chỉ cần cứng -bắt đầu JVM. Đây là một giải pháp tồi tệ, nhưng nếu bạn đang xem System.gc (), khởi động lại bắt buộc có thể là một điểm dừng có thể.
  • Xem xét sử dụng một bộ thu gom rác khác nhau. Ví dụ, trình thu thập G1 (mới trong sáu năm qua) là mô hình tạm dừng thấp; nó sử dụng tổng thể CPU nhiều hơn, nhưng tốt nhất là không bao giờ ép buộc thực thi. Vì CPU máy chủ bây giờ hầu như tất cả đều có nhiều lõi, đây là một sự đánh đổi thực sự tốt để có sẵn.
  • Nhìn vào cờ của bạn điều chỉnh bộ nhớ sử dụng. Đặc biệt là trong các phiên bản Java mới hơn, nếu bạn không có nhiều đối tượng chạy dài hạn đó, hãy xem xét tăng kích thước của newgen trong heap. newgen (trẻ) là nơi các đối tượng mới được phân bổ. Đối với máy chủ web, mọi thứ được tạo cho yêu cầu đều được đặt ở đây và nếu không gian này quá nhỏ, Java sẽ dành thêm thời gian để nâng cấp các đối tượng thành bộ nhớ tồn tại lâu hơn, trong đó chúng đắt hơn để tiêu diệt. (Nếu newgen hơi nhỏ, bạn sẽ trả tiền cho nó.) Ví dụ: trong G1:
    • XX: G1NewSizePercent (mặc định là 5; có lẽ không thành vấn đề.)
    • XX: G1MaxNewSizePercent (mặc định là 60; có thể nâng mức này lên.)
  • Cân nhắc việc nói với người thu gom rác là bạn không ổn với việc tạm dừng lâu hơn. Điều này sẽ gây ra các lần chạy GC thường xuyên hơn, cho phép hệ thống giữ các phần còn lại của nó. Trong G1:
    • XX: MaxGCPauseMillis (mặc định là 200.)

1
Nhận xét về bài đăng của riêng tôi, điều này thường không làm gì cả và việc gọi nó nhiều lần có thể khiến JVM trở nên không ổn định và không có gì. Nó cũng có thể chạy qua con chó của bạn; Cẩn thận thận trọng.
Dean J

1
Tôi sẽ nhấn mạnh vào phần "gợi ý" của "Gọi phương thức gc cho thấy nỗ lực mở rộng JVM"
matt b

2
@Jesper, câu trả lời của Dean nói "gợi ý". Trên thực tế, ông đã đăng tài liệu chính xác từ javadocs của phương thức ...
matt b

2
@Software Monkey: Vâng, tôi có thể vừa chỉnh sửa nó. Nhưng vì Dean J rõ ràng đã hoạt động (chỉ đăng một vài phút trước đây), tôi nghĩ rằng đó là một phép lịch sự để yêu cầu anh ta làm điều đó. Nếu anh ta không có, tôi sẽ quay lại đây và chỉnh sửa và xóa bình luận của tôi.
Daniel Pryden

1
Chúng tôi cũng đáng nói TẠI SAO nó không được khuyến khích. Nếu JVM chú ý đến "gợi ý" để chạy GC, nó gần như chắc chắn sẽ làm cho ứng dụng của bạn chạy chậm hơn, có thể bằng nhiều đơn đặt hàng lớn!
Stephen C

11

* "Cá nhân tôi dựa vào các biến nulling như một trình giữ chỗ để xóa thích hợp trong tương lai. Ví dụ, tôi dành thời gian để vô hiệu hóa tất cả các phần tử của một mảng trước khi thực sự xóa (tạo null) chính mảng đó."

Điều này là không cần thiết. Cách thức hoạt động của Java GC là nó tìm thấy các đối tượng không có tham chiếu đến chúng, vì vậy nếu tôi có Object x có tham chiếu (= biến) a trỏ đến nó, thì GC sẽ không xóa nó, vì có một tham chiếu đến đối tượng đó:

a -> x

Nếu bạn null a hơn điều này xảy ra:

a -> null
     x

Vì vậy, bây giờ x không có tham chiếu trỏ đến nó và sẽ bị xóa. Điều tương tự cũng xảy ra khi bạn đặt tham chiếu đến một đối tượng khác với x.

Vì vậy, nếu bạn có một mảng mảng tham chiếu đến các đối tượng x, y và z và một biến a tham chiếu đến mảng thì nó trông như thế:

a -> arr -> x
         -> y
         -> z

Nếu bạn null a hơn điều này xảy ra:

a -> null
     arr -> x
         -> y
         -> z

Vì vậy, GC tìm thấy mảng là không có tham chiếu nào được đặt cho nó và xóa nó, nó cung cấp cho bạn cấu trúc này:

a -> null
     x
     y
     z

Bây giờ, GC tìm thấy x, y và z và xóa chúng đi. Việc loại bỏ từng tham chiếu trong mảng sẽ không làm cho mọi thứ tốt hơn, nó sẽ chỉ sử dụng hết thời gian và không gian của CPU trong mã (có nghĩa là, nó sẽ không bị tổn thương nhiều hơn thế. GC vẫn có thể thực hiện theo cách nó nên ).


5

Một lý do hợp lệ cho việc muốn giải phóng bộ nhớ khỏi bất kỳ chương trình nào (java hay không) là để cung cấp thêm bộ nhớ cho các chương trình khác ở cấp hệ điều hành. Nếu ứng dụng java của tôi đang sử dụng 250MB, tôi có thể muốn giảm xuống còn 1 MB và cung cấp 249 MB cho các ứng dụng khác.


Nếu bạn cần giải phóng rõ ràng một đoạn 249 MB, trong chương trình Java, quản lý bộ nhớ sẽ không phải là điều đầu tiên tôi muốn làm việc.
Marc DiMillo

3
Nhưng việc giải phóng bộ nhớ trong heap Java của bạn không (trong trường hợp chung) làm cho bộ nhớ có sẵn cho các ứng dụng khác.
Hot Licks

5

Để mở rộng câu trả lời và nhận xét của Yianni Xanthopoulos và Hot Licks (xin lỗi, tôi chưa thể bình luận!), Bạn có thể đặt các tùy chọn VM như ví dụ này:

-XX:+UseG1GC -XX:MinHeapFreeRatio=15 -XX:MaxHeapFreeRatio=30

Trong jdk 7 của tôi, điều này sau đó sẽ giải phóng bộ nhớ VM không sử dụng nếu hơn 30% heap trở nên miễn phí sau khi GC không hoạt động. Bạn có thể sẽ cần phải điều chỉnh các tham số này.

Mặc dù tôi không thấy nó được nhấn mạnh trong liên kết bên dưới, lưu ý rằng một số trình thu gom rác có thể không tuân theo các tham số này và theo mặc định java có thể chọn một trong số chúng cho bạn, nếu bạn tình cờ có nhiều hơn một lõi (do đó, đối số UseG1GC ở trên ).

Đối số VM

Cập nhật: Đối với java 1.8.0_73, tôi đã thấy JVM thỉnh thoảng phát hành một lượng nhỏ với các cài đặt mặc định. Tuy nhiên, chỉ xuất hiện để làm điều đó nếu ~ 70% heap không được sử dụng .. không biết liệu nó có được phát hành mạnh hơn hay không nếu hệ điều hành thiếu bộ nhớ vật lý.


4

Tôi đã thực hiện thử nghiệm về điều này.

Đúng là System.gc();chỉ đề xuất chạy Trình thu gom rác.

Nhưng gọi System.gc();sau khi thiết lập tất cả các tham chiếu đến null, sẽ cải thiện hiệu suất và chiếm dụng bộ nhớ.


Tôi nghĩ bạn không thể nói chắc chắn rằng "gọi System.gc (); sau khi đặt tất cả các tham chiếu thành null, sẽ cải thiện hiệu suất và chiếm dụng bộ nhớ." Bởi vì có độ phức tạp tính toán rất lớn của System.gc (). Và ngay cả sau khi gọi System.gc () và thực sự thu thập rác, jvm có thể không trả lại bộ nhớ cho HĐH hoặc Hệ thống. JVM có thể giữ lại bộ nhớ để tham khảo trong tương lai. Xem câu trả lời này .
Md. Abu Nafee Ibna Zahid

3

Nếu bạn thực sự muốn phân bổ và giải phóng một khối bộ nhớ, bạn có thể thực hiện việc này với ByteBuffers trực tiếp. Thậm chí còn có một cách không di động để giải phóng bộ nhớ.

Tuy nhiên, như đã được đề xuất, chỉ vì bạn phải giải phóng bộ nhớ trong C, không có nghĩa là bạn nên làm điều này.

Nếu bạn cảm thấy bạn thực sự có một trường hợp sử dụng tốt miễn phí (), vui lòng bao gồm nó trong câu hỏi để chúng tôi có thể thấy những gì bạn đang muốn làm, rất có thể có một cách tốt hơn.


3

Hoàn toàn từ javacoffeebreak.com/faq/faq0012.html

Một chủ đề ưu tiên thấp sẽ tự động chăm sóc bộ sưu tập rác cho người dùng. Trong thời gian nhàn rỗi, luồng có thể được gọi và nó có thể bắt đầu giải phóng bộ nhớ trước đó cho một đối tượng trong Java. Nhưng đừng lo lắng - nó sẽ không xóa các đối tượng của bạn trên bạn!

Khi không có tài liệu tham khảo đến một đối tượng, nó trở thành trò chơi công bằng cho người thu gom rác. Thay vì gọi một số thói quen (như miễn phí trong C ++), bạn chỉ cần gán tất cả các tham chiếu cho đối tượng thành null hoặc gán một lớp mới cho tham chiếu.

Thí dụ :

public static void main(String args[])
{
  // Instantiate a large memory using class
  MyLargeMemoryUsingClass myClass = new MyLargeMemoryUsingClass(8192);

  // Do some work
  for ( .............. )
  {
      // Do some processing on myClass
  }

  // Clear reference to myClass
  myClass = null;

  // Continue processing, safe in the knowledge
  // that the garbage collector will reclaim myClass
}

Nếu mã của bạn sắp yêu cầu một lượng lớn bộ nhớ, bạn có thể muốn yêu cầu trình thu gom rác bắt đầu lấy lại không gian, thay vì cho phép nó làm như một luồng ưu tiên thấp. Để làm điều này, thêm đoạn mã sau vào mã của bạn

System.gc();

Trình thu gom rác sẽ cố gắng lấy lại dung lượng trống và ứng dụng của bạn có thể tiếp tục thực thi, với càng nhiều bộ nhớ được lấy lại càng tốt (các vấn đề phân mảnh bộ nhớ có thể áp dụng trên một số nền tảng nhất định).


1

Trong trường hợp của tôi, vì mã Java của tôi có nghĩa là được chuyển sang các ngôn ngữ khác trong tương lai gần (Chủ yếu là C ++), tôi ít nhất muốn trả tiền dịch vụ môi để giải phóng bộ nhớ đúng cách để nó giúp quá trình chuyển tiếp sau này.

Cá nhân tôi dựa vào các biến nulling như một trình giữ chỗ để xóa thích hợp trong tương lai. Ví dụ, tôi dành thời gian để vô hiệu hóa tất cả các phần tử của một mảng trước khi thực sự xóa (tạo null) chính mảng đó.

Nhưng trường hợp của tôi rất đặc biệt và tôi biết tôi đang đạt được thành tích khi thực hiện điều này.


1

* "Ví dụ: giả sử bạn đã khai báo một Danh sách khi bắt đầu một phương thức có kích thước rất lớn, nhưng chỉ được yêu cầu cho đến khi đi được một nửa trong phương thức. Lúc này, bạn có thể đặt tham chiếu Danh sách thành null để cho phép trình thu gom rác có khả năng lấy lại đối tượng này trước khi phương thức hoàn thành (và dù sao tham chiếu cũng nằm ngoài phạm vi). " *

Điều này là chính xác, nhưng giải pháp này có thể không khái quát. Trong khi đặt tham chiếu đối tượng Danh sách thành null -will- cung cấp bộ nhớ cho bộ sưu tập rác, điều này chỉ đúng với đối tượng Danh sách các kiểu nguyên thủy. Nếu đối tượng Danh sách thay vì chứa các loại tham chiếu, việc đặt đối tượng Danh sách = null sẽ không hủy đăng ký -any- của các loại tham chiếu có trong danh sách. Trong trường hợp này, việc đặt đối tượng List = null sẽ mồ côi các loại tham chiếu có chứa đối tượng sẽ không có sẵn để thu gom rác trừ khi thuật toán thu gom rác đủ thông minh để xác định rằng các đối tượng đã mồ côi.


1
Điều này thực sự là không đúng sự thật. Trình thu gom rác Java đủ thông minh để xử lý chính xác. Nếu bạn vô hiệu hóa Danh sách (và các đối tượng trong Danh sách không có các tham chiếu khác đến chúng), thì GC có thể lấy lại tất cả các đối tượng trong Danh sách. Nó có thể chọn không làm điều đó vào thời điểm hiện tại, nhưng cuối cùng nó sẽ đòi lại chúng. Cùng đi cho các tài liệu tham khảo theo chu kỳ. Về cơ bản, cách thức hoạt động của GC là tìm kiếm các đối tượng mồ côi một cách bí mật và sau đó lấy lại chúng. Đây là toàn bộ công việc của một GC. Cách bạn mô tả nó sẽ khiến cho một GC hoàn toàn vô dụng.
Dakkaron

1

Java liên tục cung cấp bộ sưu tập rác tự động đôi khi bạn sẽ muốn biết đối tượng lớn bao nhiêu và còn lại bao nhiêu. Bộ nhớ miễn phí sử dụng theo chương trình import java.lang;Runtime r=Runtime.getRuntime();để có được các giá trị của bộ nhớ sử dụng mem1=r.freeMemory();để giải phóng bộ nhớ r.gc();và gọifreeMemory()


1

Khuyến nghị từ JAVA là gán cho null

Từ https://docs.oracle.com/cd/E19159-01/819-3681/abebi/index.html

Việc gán một giá trị null cho các biến không còn cần thiết sẽ giúp bộ thu gom rác xác định các phần của bộ nhớ có thể được thu hồi một cách an toàn. Mặc dù Java cung cấp quản lý bộ nhớ, nhưng nó không ngăn chặn rò rỉ bộ nhớ hoặc sử dụng quá nhiều bộ nhớ.

Một ứng dụng có thể gây rò rỉ bộ nhớ bằng cách không phát hành các tham chiếu đối tượng. Làm như vậy sẽ ngăn trình thu gom rác Java lấy lại các đối tượng đó và dẫn đến việc tăng lượng bộ nhớ được sử dụng. Hoàn toàn vô hiệu hóa các tham chiếu đến các biến sau khi sử dụng chúng cho phép trình thu gom rác lấy lại bộ nhớ.

Một cách để phát hiện rò rỉ bộ nhớ là sử dụng các công cụ định hình và chụp ảnh nhanh bộ nhớ sau mỗi giao dịch. Một ứng dụng không bị rò rỉ ở trạng thái ổn định sẽ hiển thị bộ nhớ heap hoạt động ổn định sau các bộ sưu tập rác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.