Tại sao việc gọi System.gc () là không tốt?


326

Sau khi trả lời một câu hỏi về cách buộc các đối tượng không có lực trong Java (anh chàng đang xóa HashMap 1,5 GB) System.gc(), tôi được cho biết đó là cách thực hành tồi để gọi System.gc()thủ công, nhưng các ý kiến ​​không hoàn toàn thuyết phục. Ngoài ra, dường như không ai dám upvote, cũng không downvote câu trả lời của tôi.

Tôi được thông báo rằng đó là một thực tiễn tồi tệ, nhưng sau đó tôi cũng được thông báo rằng các hoạt động thu gom rác không dừng lại một cách có hệ thống thế giới nữa và JVM cũng có thể được sử dụng một cách hiệu quả như một gợi ý, vì vậy tôi thuộc loại mất mát.

Tôi hiểu rằng JVM thường biết rõ hơn bạn khi cần lấy lại bộ nhớ. Tôi cũng hiểu rằng lo lắng về một vài kilobyte dữ liệu là ngớ ngẩn. Tôi cũng hiểu rằng ngay cả megabyte dữ liệu cũng không phải là vài năm trước. Nhưng vẫn còn 1,5 gigabyte? Và bạn biết có khoảng 1,5 GB dữ liệu bị treo trong bộ nhớ; Nó không giống như nó là một shot trong bóng tối. Là System.gc()hệ thống xấu, hoặc có một số điểm mà tại đó nó trở nên ổn?

Vì vậy, câu hỏi thực sự là gấp đôi:

  • Tại sao hoặc không phải là thực hành xấu để gọi System.gc()? Nó thực sự chỉ là một gợi ý cho JVM theo các triển khai nhất định hay nó luôn luôn là một chu trình thu thập đầy đủ? Có thực sự thu gom rác thực hiện công việc của họ mà không dừng lại trên thế giới? Xin hãy làm sáng tỏ những khẳng định khác nhau mà mọi người đã đưa ra trong các bình luận cho câu trả lời của tôi .
  • Ngưỡng ở đâu? Có bao giờ là một ý tưởng tốt để gọi System.gc(), hoặc có những lúc nó được chấp nhận? Nếu vậy, những lúc đó là gì?

4
Tôi nghĩ rằng thời điểm tốt để gọi System.gc () là khi bạn đang thực hiện một quá trình tải dài. Ví dụ: tôi đang làm việc trên một trò chơi và dự định gọi System.gc () khi trò chơi tải một cấp độ mới, vào cuối quá trình tải. Người dùng đã chờ đợi một chút và việc tăng hiệu suất tăng thêm có thể đáng giá; nhưng tôi cũng sẽ đặt một tùy chọn trong màn hình cấu hình để vô hiệu hóa hành vi này.
Ricket

41
Bozho: lưu ý từ đầu tiên của câu hỏi. TẠI SAO tốt nhất là không gọi nó? Chỉ cần lặp lại một câu thần chú không giải thích được một điều đáng nguyền rủa.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

1
Một trường hợp khác đã được giải thích trong java-monitor.com/forum/showthread.php?t=188 Trường hợp giải thích làm thế nào có thể thực hành tồi để gọi System.gc ()
Shirishkumar Bari

Câu trả lời:


243

Lý do tất cả mọi người luôn nói để tránh System.gc()là nó là một chỉ số khá tốt về mã bị hỏng cơ bản . Bất kỳ mã nào phụ thuộc vào nó cho chính xác chắc chắn bị hỏng; bất kỳ cái gì dựa vào nó cho hiệu suất rất có thể bị hỏng.

Bạn không biết bạn đang chạy loại rác nào. Chắc chắn có một số thứ không "ngăn chặn thế giới" như bạn khẳng định, nhưng một số JVM không thông minh hoặc vì nhiều lý do (có lẽ chúng ở trên điện thoại?) Không làm điều đó. Bạn không biết nó sẽ làm gì.

Ngoài ra, nó không được đảm bảo để làm bất cứ điều gì. JVM có thể hoàn toàn bỏ qua yêu cầu của bạn.

Sự kết hợp của "bạn không biết nó sẽ làm gì", "bạn không biết liệu nó có giúp ích gì không" và "dù sao bạn cũng không cần phải gọi nó" là lý do tại sao mọi người rất mạnh mẽ khi nói rằng bạn không nên gọi nó. Tôi nghĩ đó là một trường hợp "nếu bạn cần hỏi liệu bạn có nên sử dụng cái này không, bạn không nên"


BIÊN TẬP để giải quyết một số mối quan tâm từ các chủ đề khác:

Sau khi đọc chủ đề bạn liên kết, có một vài điều nữa tôi muốn chỉ ra. Đầu tiên, ai đó đề nghị rằng việc gọi gc()có thể trả lại bộ nhớ cho hệ thống. Điều đó chắc chắn không đúng - bản thân đống Java phát triển độc lập với phân bổ Java.

Như trong, JVM sẽ giữ bộ nhớ (nhiều hàng chục megabyte) và tăng số đống khi cần thiết. Nó không nhất thiết phải trả lại bộ nhớ đó cho hệ thống ngay cả khi bạn giải phóng các đối tượng Java; nó hoàn toàn miễn phí để giữ bộ nhớ được phân bổ để sử dụng cho các phân bổ Java trong tương lai.

Để cho thấy rằng nó có thể System.gc() không có gì, hãy xem:

http://bugs.sun.com/view_orms.do?orms_id=6668279

và đặc biệt là có tùy chọn VM -XX: DisableExplicitGC .


1
Bạn có thể xây dựng một số thiết lập Rube Goldberg-esque kỳ lạ trong đó phương thức chạy GC ảnh hưởng đến tính chính xác của mã của bạn. Có lẽ nó che giấu một số tương tác luồng lạ, hoặc có thể một bộ hoàn thiện có ảnh hưởng đáng kể đến việc chạy chương trình. Tôi không hoàn toàn chắc chắn điều đó có thể xảy ra nhưng có thể là vậy, vì vậy tôi nghĩ rằng tôi đã đề cập đến nó.
Steven Schlansker

2
@zneak, ví dụ, bạn có thể đã đặt mã quan trọng vào bộ hoàn thiện (mã bị hỏng cơ bản)
Martin

3
Tôi muốn nói thêm rằng có một vài trường hợp góc System.gc()rất hữu ích và thậm chí có thể cần thiết. Ví dụ: trong các ứng dụng UI trên Windows, nó có thể tăng tốc đáng kể quá trình khôi phục của Window khi bạn gọi System.gc () trước khi bạn thu nhỏ Window (đặc biệt là khi nó bị thu nhỏ trong một thời gian và các phần của quá trình bị hoán đổi đĩa).
Joachim Sauer

2
@AndrewJanke Tôi muốn nói mã sử dụng WeakReferences cho các đối tượng bạn muốn giữ là không chính xác từ đầu, bộ sưu tập rác hay không. Bạn sẽ gặp vấn đề tương tự trong C ++ std::weak_ptr(mặc dù bạn có thể nhận thấy sự cố trong phiên bản C ++ sớm hơn so với phiên bản Java vì việc hủy bỏ đối tượng sẽ không được hoãn như hoàn thiện thường).
JAB

2
@rebeccah Đó là một lỗi, vì vậy, tôi sẽ gọi nó là 'chắc chắn bị hỏng'. Thực tế là System.gc()sửa nó là một cách giải quyết, không phải là thực hành mã hóa tốt.
Steven Schlansker

149

Nó đã được giải thích rằng có thể gọisystem.gc() không làm gì cả và bất kỳ mã nào "cần" trình thu gom rác để chạy đều bị hỏng.

Tuy nhiên, lý do thực tế mà nó là thực tiễn tồi để gọi System.gc()là nó không hiệu quả. Và trong trường hợp xấu nhất, nó không hiệu quả khủng khiếp ! Hãy để tôi giải thích.

Một thuật toán GC điển hình xác định rác bằng cách duyệt qua tất cả các đối tượng không phải rác trong heap và suy ra rằng bất kỳ đối tượng nào không được truy cập đều phải là rác. Từ đó, chúng ta có thể mô hình hóa toàn bộ công việc của bộ sưu tập rác bao gồm một phần tỷ lệ thuận với lượng dữ liệu trực tiếp và một phần khác tỷ lệ thuận với lượng rác; I Ework = (live * W1 + garbage * W2) .

Bây giờ giả sử rằng bạn làm như sau trong một ứng dụng đơn luồng.

System.gc(); System.gc();

Cuộc gọi đầu tiên sẽ (chúng tôi dự đoán) sẽ làm (live * W1 + garbage * W2) công việc và loại bỏ rác thải nổi bật.

Cuộc gọi thứ hai sẽ làm (live* W1 + 0 * W2)việc và không đòi lại gì. Nói cách khác, chúng tôi đã hoàn thành (live * W1)công việc và hoàn toàn không đạt được gì .

Chúng ta có thể mô hình hóa hiệu quả của người thu gom là khối lượng công việc cần thiết để thu gom một đơn vị rác; tức efficiency = (live * W1 + garbage * W2) / garbage. Vì vậy, để làm cho GC hiệu quả nhất có thể, chúng ta cần tối đa hóa giá trị garbagekhi chúng ta chạy GC; tức là đợi cho đến khi đống đầy. (Và cũng, làm cho đống lớn nhất có thể. Nhưng đó là một chủ đề riêng biệt.)

Nếu ứng dụng không can thiệp (bằng cách gọi System.gc()), GC sẽ đợi cho đến khi heap đầy trước khi chạy, dẫn đến việc thu gom rác hiệu quả 1 . Nhưng nếu ứng dụng buộc GC chạy, nhiều khả năng là đống không đầy, và kết quả là rác sẽ được thu gom không hiệu quả. Và ứng dụng càng thường xuyên buộc GC, càng trở nên kém hiệu quả.

Lưu ý: lời giải thích ở trên nhấn mạnh đến thực tế rằng một phân vùng GC hiện đại điển hình phân vùng heap thành "khoảng trắng", GC có thể tự động mở rộng vùng heap, tập hợp các đối tượng không rác của ứng dụng có thể khác nhau, v.v. Mặc dù vậy, cùng một hiệu trưởng cơ bản áp dụng cho tất cả các ban thu gom rác thực sự 2 . Đó là không hiệu quả để buộc các GC chạy.


1 - Đây là cách bộ sưu tập "thông lượng" hoạt động. Các trình thu gom đồng thời như CMS và G1 sử dụng các tiêu chí khác nhau để quyết định khi nào bắt đầu trình thu gom rác.

2 - Tôi cũng loại trừ các trình quản lý bộ nhớ chỉ sử dụng tính năng tham chiếu, nhưng không có triển khai Java hiện tại nào sử dụng cách tiếp cận đó ... vì lý do chính đáng.


45
+1 Giải thích tốt. Tuy nhiên, lưu ý rằng lý do này chỉ áp dụng nếu bạn quan tâm đến thông lượng. Nếu bạn muốn tối ưu hóa độ trễ tại các điểm cụ thể, việc buộc GC có thể có ý nghĩa. Ví dụ (nói theo giả thuyết) trong một trò chơi, bạn có thể muốn tránh sự chậm trễ trong các cấp độ, nhưng bạn không quan tâm đến sự chậm trễ trong quá trình tải cấp. Sau đó, nó sẽ có ý nghĩa để buộc GC sau khi tải mức. Nó không làm giảm thông lượng tổng thể, nhưng đó không phải là những gì bạn đang tối ưu hóa.
sleske

2
@sleske - những gì bạn nói là đúng. Tuy nhiên, chạy GC giữa các cấp là một giải pháp hỗ trợ băng tần ... và không giải quyết được vấn đề về độ trễ nếu các cấp độ đủ lâu để bạn cần chạy GC trong một cấp độ. Cách tiếp cận tốt hơn là sử dụng trình thu gom rác đồng thời (tạm dừng thấp) ... nếu nền tảng hỗ trợ nó.
Stephen C

Không hoàn toàn chắc chắn những gì bạn có nghĩa là "cần người thu gom rác để chạy". Các điều kiện ứng dụng cần tránh là; thất bại phân bổ không bao giờ có thể được thỏa mãn và chi phí cao. Việc gọi ngẫu nhiên tới System.gc () thường dẫn đến chi phí đầu tư cao.
Kirk

@Kirk - "Thực hiện các cuộc gọi ngẫu nhiên tới System.gc () thường dẫn đến chi phí hoạt động cao." . Tôi biết điều đó. Vì vậy, bất cứ ai đã đọc và hiểu câu trả lời của tôi.
Stephen C

@Kirk - "Không hoàn toàn chắc chắn ý của bạn là gì ..." - Ý tôi là các chương trình trong đó hành vi "đúng" của chương trình phụ thuộc vào GC chạy vào một thời điểm cụ thể; ví dụ: để thực thi quyết toán, hoặc phá vỡ WeakReferences hoặc SoftReferences. Đó là một ý tưởng thực sự tồi tệ để làm điều này ... nhưng đây là những gì tôi đang nói về. Xem thêm câu trả lời của Steven Schlansk.
Stephen C

47

Rất nhiều người dường như đang bảo bạn đừng làm điều này. Tôi không đồng ý. Nếu, sau một quá trình tải lớn như tải một cấp, bạn tin rằng:

  1. Bạn có rất nhiều đối tượng không thể truy cập và có thể chưa được gc'ed. và
  2. Bạn nghĩ rằng người dùng có thể đưa ra một sự chậm lại nhỏ vào thời điểm này

không có hại khi gọi System.gc (). Tôi nhìn nó giống như inlinetừ khóa c / c ++ . Đó chỉ là một gợi ý cho gc rằng bạn, nhà phát triển, đã quyết định rằng thời gian / hiệu suất không quan trọng như thường lệ và một số có thể được sử dụng để lấy lại bộ nhớ.

Lời khuyên không nên dựa vào nó làm bất cứ điều gì là chính xác. Đừng dựa vào nó để làm việc, nhưng đưa ra gợi ý rằng bây giờ là thời gian có thể chấp nhận để thu thập là hoàn toàn tốt. Tôi thà lãng phí thời gian tại một điểm trong mã không quan trọng (tải màn hình) hơn là khi người dùng đang tích cực tương tác với chương trình (như trong một cấp độ của trò chơi.)

Có một lần tôi sẽ buộc thu thập: khi cố gắng tìm hiểu là một đối tượng cụ thể bị rò rỉ (mã gốc hoặc tương tác gọi lại phức tạp, lớn. Oh và bất kỳ thành phần UI nào liếc qua Matlab.) Không bao giờ nên sử dụng trong mã sản xuất.


3
+1 cho GC trong khi phân tích cho rò rỉ mem. Lưu ý rằng thông tin về việc sử dụng heap (Runtime.freeMemory () và cộng sự) thực sự chỉ có ý nghĩa sau khi buộc một GC, nếu không, nó sẽ phụ thuộc vào lần cuối khi hệ thống làm phiền khi chạy một GC.
sleske

4
không có hại gì khi gọi System.gc () nó có thể yêu cầu stop the worldtiếp cận và đây là một tác hại thực sự, nếu xảy ra
bestsss

7
tác hại trong việc kêu gọi các nhà sưu tập rác một cách rõ ràng. Gọi GC không đúng lúc sẽ làm lãng phí chu kỳ CPU. Bạn (lập trình viên) không có đủ thông tin để xác định thời điểm thích hợp là ... nhưng JVM thì có.
Stephen C

Jetty thực hiện 2 cuộc gọi tới System.gc () sau khi khởi động và tôi hiếm khi thấy nó tạo ra sự khác biệt nào.
Kirk

Các nhà phát triển của Jetty có một lỗi. Nó sẽ tạo ra sự khác biệt ... ngay cả khi sự khác biệt khó định lượng.
Stephen C

31

Mọi người đang làm tốt công việc giải thích tại sao KHÔNG sử dụng, vì vậy tôi sẽ kể cho bạn một vài tình huống mà bạn nên sử dụng nó:

(Các ý kiến ​​sau đây áp dụng cho Hotspot chạy trên Linux với trình thu thập CMS, nơi tôi cảm thấy tự tin nói rằng System.gc()thực tế luôn luôn gọi một bộ sưu tập rác đầy đủ).

  1. Sau công việc ban đầu để khởi động ứng dụng của bạn, bạn có thể là một trạng thái sử dụng bộ nhớ khủng khiếp. Một nửa thế hệ được thuê của bạn có thể chứa đầy rác, có nghĩa là bạn gần với CMS đầu tiên của bạn hơn. Trong các ứng dụng có vấn đề, không nên gọi System.gc () để "đặt lại" heap của bạn về trạng thái bắt đầu của dữ liệu trực tiếp.

  2. Dọc theo cùng dòng với # 1, nếu bạn theo dõi chặt chẽ việc sử dụng heap của mình, bạn muốn có một cách đọc chính xác về mức sử dụng bộ nhớ cơ sở của bạn. Nếu 2 phút đầu tiên của thời gian hoạt động của ứng dụng là tất cả khởi tạo, dữ liệu của bạn sẽ bị rối trừ khi bạn buộc (ahem ... "đề nghị") toàn bộ gc lên phía trước.

  3. Bạn có thể có một ứng dụng được thiết kế để không bao giờ quảng bá bất cứ điều gì cho thế hệ được thuê trong khi nó đang chạy. Nhưng có lẽ bạn cần khởi tạo một số dữ liệu không quá lớn để tự động được chuyển sang thế hệ được thuê. Trừ khi bạn gọi System.gc () sau khi mọi thứ được thiết lập, dữ liệu của bạn có thể nằm ở thế hệ mới cho đến khi đến lúc nó được quảng bá. Thật bất ngờ, ứng dụng GC độ trễ thấp, siêu lừa đảo của bạn bị ảnh hưởng bởi một hình phạt độ trễ HUGE (tương đối, tất nhiên) để thúc đẩy các đối tượng đó trong các hoạt động bình thường.

  4. Đôi khi rất hữu ích khi có một lệnh gọi System.gc trong ứng dụng sản xuất để xác minh sự tồn tại của rò rỉ bộ nhớ. Nếu bạn biết rằng tập hợp dữ liệu trực tiếp tại thời điểm X nên tồn tại theo một tỷ lệ nhất định với tập hợp dữ liệu trực tiếp tại thời điểm Y, thì có thể hữu ích khi gọi System.gc () là thời gian X và thời gian Y và so sánh việc sử dụng bộ nhớ .


1
Đối với hầu hết các công cụ thu gom rác thế hệ, các đối tượng trong thế hệ mới phải tồn tại một số lượng bộ sưu tập rác nhất định (thường có thể định cấu hình), vì vậy, gọi System.gc()một lần để buộc quảng cáo các đối tượng không giúp bạn được gì. Và bạn chắc chắn không muốn gọi System.gc()tám lần liên tiếp và cầu nguyện rằng bây giờ, chương trình khuyến mãi đã được thực hiện và chi phí tiết kiệm cho lần quảng cáo sau này biện minh cho chi phí của nhiều GC đầy đủ. Tùy thuộc vào thuật toán GC, việc quảng bá nhiều đối tượng thậm chí có thể không phải chịu chi phí thực tế, vì nó sẽ chỉ gán lại bộ nhớ cho thế hệ cũ hoặc sao chép đồng thời trên đường
Holger

11

Đây là một câu hỏi rất khó chịu và tôi cảm thấy góp phần khiến nhiều người phản đối Java mặc dù ngôn ngữ này hữu ích như thế nào.

Việc bạn không thể tin tưởng "System.gc" để làm bất cứ điều gì là cực kỳ nan giải và có thể dễ dàng tạo ra cảm giác "Sợ hãi, không chắc chắn, nghi ngờ" đối với ngôn ngữ.

Trong nhiều trường hợp, thật tuyệt khi xử lý các xung đột bộ nhớ mà bạn cố tình gây ra trước khi một sự kiện quan trọng xảy ra, điều này sẽ khiến người dùng nghĩ rằng chương trình của bạn được thiết kế kém / không phản hồi.

Có khả năng kiểm soát bộ sưu tập rác sẽ là một công cụ giáo dục tuyệt vời, đến lượt nó giúp mọi người hiểu cách thức hoạt động của bộ sưu tập rác và cách làm cho các chương trình khai thác hành vi mặc định cũng như hành vi được kiểm soát.

Hãy để tôi xem lại các đối số của chủ đề này.

  1. Đó là không hiệu quả:

Thông thường, chương trình có thể không làm gì cả và bạn biết nó không làm gì cả vì cách nó được thiết kế. Ví dụ, nó có thể phải chờ đợi lâu với hộp thông báo chờ lớn và cuối cùng, nó cũng có thể thêm một cuộc gọi để thu gom rác vì thời gian để chạy sẽ mất một phần rất nhỏ thời gian chờ đợi lâu nhưng sẽ tránh gc hoạt động ở giữa một hoạt động quan trọng hơn.

  1. Nó luôn luôn là một thực hành xấu và chỉ ra mã bị hỏng.

Tôi không đồng ý, bạn không có vấn đề gì với công cụ thu gom rác. Công việc của nó là theo dõi rác và làm sạch nó.

Bằng cách gọi gc trong thời gian sử dụng ít quan trọng hơn, bạn giảm tỷ lệ chạy khi cuộc sống của bạn phụ thuộc vào mã cụ thể đang chạy nhưng thay vào đó, nó quyết định thu gom rác.

Chắc chắn, nó có thể không hoạt động theo cách bạn muốn hoặc mong đợi, nhưng khi bạn muốn gọi nó, bạn biết không có gì xảy ra và người dùng sẵn sàng chịu đựng sự chậm chạp / thời gian chết. Nếu System.gc hoạt động, thật tuyệt! Nếu không, ít nhất bạn đã thử. Đơn giản là không có mặt trái trừ khi bộ thu gom rác có tác dụng phụ vốn có gây ra điều gì đó bất ngờ khủng khiếp đối với cách người thu gom rác giả sử hành xử nếu được gọi thủ công và chính điều này gây ra sự mất lòng tin.

  1. Đây không phải là trường hợp sử dụng phổ biến:

Đó là một trường hợp sử dụng không thể đạt được một cách đáng tin cậy, nhưng có thể là nếu hệ thống được thiết kế theo cách đó. Nó giống như làm đèn giao thông và làm cho nó để một số / tất cả các nút của đèn giao thông không làm gì cả, nó khiến bạn đặt câu hỏi tại sao nút này lại bắt đầu, javascript không có chức năng thu gom rác nên chúng tôi không 'xem xét kỹ lưỡng nó nhiều như nó.

  1. Thông số kỹ thuật nói rằng System.gc () là một gợi ý rằng GC nên chạy và VM có thể tự do bỏ qua nó.

một "gợi ý" là gì? "bỏ qua" là gì? một máy tính không thể đơn giản đưa ra gợi ý hoặc bỏ qua một cái gì đó, có những đường dẫn hành vi nghiêm ngặt mà nó có thể là động được hướng dẫn bởi ý định của hệ thống. Một câu trả lời thích hợp sẽ bao gồm những gì trình thu gom rác thực sự đang làm, ở cấp độ thực thi, khiến nó không thực hiện việc thu thập khi bạn yêu cầu. Là tính năng đơn giản là một nop? Có một số điều kiện mà tôi phải đáp ứng? Những điều kiện này là gì?

Như hiện tại, GC của Java thường trông giống như một con quái vật mà bạn không tin tưởng. Bạn không biết khi nào nó sẽ đến hay đi, bạn không biết nó sẽ làm gì, nó sẽ làm như thế nào. Tôi có thể tưởng tượng một số chuyên gia có ý tưởng tốt hơn về cách Bộ sưu tập Rác của họ hoạt động trên cơ sở theo hướng dẫn, nhưng đại đa số chỉ đơn giản hy vọng nó "chỉ hoạt động" và phải tin vào một thuật toán có vẻ mờ đục để làm việc cho bạn thật khó chịu.

Có một khoảng cách lớn giữa việc đọc về một cái gì đó hoặc được dạy một cái gì đó, và thực sự thấy việc thực hiện nó, sự khác biệt giữa các hệ thống và có thể chơi với nó mà không cần phải xem mã nguồn. Điều này tạo ra sự tự tin và cảm giác làm chủ / hiểu / kiểm soát.

Tóm lại, có một vấn đề cố hữu với câu trả lời "tính năng này có thể không làm gì cả và tôi sẽ không đi vào chi tiết làm thế nào để biết khi nào nó làm gì và khi nào nó không và tại sao nó sẽ không hoặc sẽ, thường ngụ ý rằng nó chỉ đơn giản là chống lại triết lý để cố gắng thực hiện nó, ngay cả khi ý định đằng sau nó là hợp lý ".

Java có thể ổn khi hành xử theo cách của nó, hoặc có thể không, nhưng để hiểu nó, thật khó để thực sự đi theo hướng nào để có được một cái nhìn tổng quan về những gì bạn có thể tin tưởng vào GC để làm và không nên làm, vì vậy quá dễ dàng chỉ đơn giản là không tin vào ngôn ngữ, vì mục đích của ngôn ngữ là kiểm soát hành vi đến mức độ triết học (dễ lập trình viên, đặc biệt là người mới rơi vào khủng hoảng tồn tại từ một số hành vi hệ thống / ngôn ngữ nhất định) có khả năng chịu đựng (và nếu bạn không thể, bạn sẽ không sử dụng ngôn ngữ cho đến khi bạn phải) và nhiều thứ khác bạn không thể kiểm soát mà không biết lý do tại sao bạn không thể kiểm soát chúng vốn có hại.


9

Hiệu quả của GC phụ thuộc vào một số phương pháp phỏng đoán. Ví dụ, một heuristic phổ biến là ghi truy cập vào các đối tượng thường xảy ra trên các đối tượng được tạo ra cách đây không lâu. Một điều nữa là nhiều đối tượng có thời gian tồn tại rất ngắn (một số đối tượng sẽ được sử dụng trong một thời gian dài, nhưng nhiều đối tượng sẽ bị loại bỏ một vài micro giây sau khi tạo ra chúng).

Gọi System.gc()giống như đá vào GC. Nó có nghĩa là: "tất cả những thông số được điều chỉnh cẩn thận, những tổ chức thông minh đó, tất cả những nỗ lực bạn bỏ ra để phân bổ và quản lý các đối tượng sao cho mọi việc diễn ra suôn sẻ, tốt, chỉ cần bỏ toàn bộ và bắt đầu lại từ đầu". Nó có thể cải thiện hiệu suất, nhưng hầu hết thời gian nó chỉ làm giảm hiệu suất.

Để sử dụng System.gc() một cách đáng tin cậy (*), bạn cần biết cách thức hoạt động của GC trong tất cả các chi tiết tốt của nó. Các chi tiết như vậy có xu hướng thay đổi khá nhiều nếu bạn sử dụng JVM từ nhà cung cấp khác hoặc phiên bản tiếp theo từ cùng một nhà cung cấp hoặc cùng một JVM nhưng với các tùy chọn dòng lệnh hơi khác nhau. Vì vậy, nó hiếm khi là một ý tưởng tốt, trừ khi bạn muốn giải quyết một vấn đề cụ thể trong đó bạn kiểm soát tất cả các tham số đó. Do đó khái niệm "thực hành xấu": điều đó không bị cấm, phương pháp tồn tại, nhưng nó hiếm khi được đền đáp.

(*) Tôi đang nói về hiệu quả ở đây. System.gc()sẽ không bao giờ phá vỡ một chương trình Java chính xác. Nó sẽ không gợi thêm bộ nhớ mà JVM không thể có được bằng cách khác: trước khi ném một OutOfMemoryError, JVM thực hiện công việc của nó System.gc(), ngay cả khi là phương sách cuối cùng.


1
+1 khi đề cập rằng System.gc () không ngăn OutOfMemoryError. Một số người tin điều này.
sleske

1
Trên thực tế, nó có thể ngăn OutOfMemoryError vì xử lý các tham chiếu mềm. SoftReferences được tạo sau lần chạy GC cuối cùng không được thu thập trong quá trình triển khai mà tôi biết. Nhưng đây là chi tiết triển khai có thể thay đổi bất cứ lúc nào và một loại lỗi và không có gì bạn nên dựa vào.
maaartinus

8

Đôi khi ( không thường xuyên! ) Bạn thực sự biết nhiều hơn về việc sử dụng bộ nhớ trong quá khứ, hiện tại và tương lai sau đó thời gian chạy. Điều này không xảy ra rất thường xuyên và tôi sẽ tuyên bố không bao giờ trong ứng dụng web trong khi các trang bình thường đang được phục vụ.

Nhiều năm trước tôi làm việc trên một trình tạo báo cáo, rằng

  • Có một chủ đề duy nhất
  • Đọc báo cáo yêu cầu trên mạng từ một hàng đợi
  • Đã tải dữ liệu cần thiết cho báo cáo từ cơ sở dữ liệu
  • Tạo báo cáo và gửi email ra.
  • Lặp đi lặp lại mãi mãi, ngủ khi không có yêu cầu nổi bật.
  • Nó không sử dụng lại bất kỳ dữ liệu nào giữa các báo cáo và không thực hiện bất kỳ khoản tiền mặt nào.

Thứ nhất, vì đây không phải là thời gian thực và người dùng dự kiến ​​sẽ chờ báo cáo, sự chậm trễ trong khi chạy GC không phải là vấn đề, nhưng chúng tôi cần tạo báo cáo với tốc độ nhanh hơn yêu cầu.

Nhìn vào phác thảo trên của quá trình, rõ ràng rằng.

  • Chúng tôi biết sẽ có rất ít đối tượng sống ngay sau khi một báo cáo được gửi qua email, vì yêu cầu tiếp theo chưa bắt đầu được xử lý.
  • Người ta biết rằng chi phí để chạy một chu trình thu gom rác phụ thuộc vào số lượng đối tượng sống, lượng rác ít ảnh hưởng đến chi phí của một lần chạy GC.
  • Rằng khi hàng đợi trống, không có gì tốt hơn để làm sau đó chạy GC.

Do đó, rõ ràng nó rất có giá trị trong khi thực hiện chạy GC bất cứ khi nào hàng đợi yêu cầu trống; không có nhược điểm này

Có thể đáng để thực hiện một lần chạy GC sau khi mỗi báo cáo được gửi qua email, vì chúng tôi biết đây là thời điểm tốt để chạy GC. Tuy nhiên, nếu máy tính có đủ ram, sẽ thu được kết quả tốt hơn bằng cách trì hoãn việc chạy GC.

Hành vi này được định cấu hình trên mỗi cơ sở cài đặt, đối với một số khách hàng kích hoạt tính năng bắt buộc sau mỗi báo cáo được tăng tốc đáng kể việc bảo vệ báo cáo. (Tôi hy vọng điều này là do bộ nhớ thấp trên máy chủ của họ và nó chạy rất nhiều quá trình khác, do đó, thời gian tốt đã buộc GC giảm phân trang.)

Chúng tôi không bao giờ phát hiện ra một bản cài đặt không mang lại lợi ích là một hoạt động bắt buộc của GC mỗi khi hàng đợi công việc trống.

Nhưng, hãy rõ ràng, ở trên không phải là một trường hợp phổ biến.


3

Có thể tôi viết mã tào lao, nhưng tôi nhận ra rằng việc nhấp vào biểu tượng thùng rác trên nhật thực và IDE netbeans là một 'cách thực hành tốt'.


1
Điều này có thể là như vậy. Nhưng nếu Eclipse hoặc NetBeans được lập trình để gọi System.gc()định kỳ, có lẽ bạn sẽ thấy hành vi đó gây phiền nhiễu.
Stephen C

2

Đầu tiên, có một sự khác biệt giữa thông số kỹ thuật và thực tế. Thông số kỹ thuật nói rằng System.gc () là một gợi ý rằng GC nên chạy và VM có thể tự do bỏ qua nó. Thực tế là, VM sẽ không bao giờ bỏ qua lệnh gọi System.gc ().

Gọi cho GC đi kèm với một chi phí không hề nhỏ cho cuộc gọi và nếu bạn thực hiện việc này vào một thời điểm ngẫu nhiên, có thể bạn sẽ không thấy phần thưởng cho những nỗ lực của mình. Mặt khác, một bộ sưu tập được kích hoạt tự nhiên rất có khả năng thu lại chi phí của cuộc gọi. Nếu bạn có thông tin chỉ ra rằng một GC nên được chạy hơn bạn có thể thực hiện cuộc gọi đến System.gc () và bạn sẽ thấy lợi ích. Tuy nhiên, theo kinh nghiệm của tôi, điều này chỉ xảy ra trong một vài trường hợp cạnh vì rất khó có khả năng bạn sẽ có đủ thông tin để hiểu nếu và khi nào nên gọi System.gc ().

Một ví dụ được liệt kê ở đây, nhấn thùng rác trong IDE của bạn. Nếu bạn đang đi đến một cuộc họp tại sao không nhấn nó. Chi phí sẽ không ảnh hưởng đến bạn và đống có thể được dọn sạch khi bạn quay lại. Làm điều này trong một hệ thống sản xuất và các cuộc gọi thường xuyên để thu thập sẽ khiến nó bị đình trệ! Ngay cả các cuộc gọi không thường xuyên như cuộc gọi do RMI thực hiện cũng có thể làm gián đoạn hiệu suất.


3
"Thực tế là, VM sẽ không bao giờ bỏ qua cuộc gọi đến System.gc ()." - Không chính xác. Đọc về -XX: -DisableExplicitGC.
Stephen C

1

Có, việc gọi System.gc () không đảm bảo rằng nó sẽ chạy, đó là một yêu cầu đối với JVM có thể bị bỏ qua. Từ các tài liệu:

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

Gọi nó gần như luôn luôn là một ý tưởng tồi bởi vì quản lý bộ nhớ tự động thường biết rõ hơn bạn khi nào gc. Nó sẽ làm như vậy khi nhóm bộ nhớ trống bên trong của nó thấp hoặc nếu HĐH yêu cầu một số bộ nhớ được trả lại.

Có thể chấp nhận gọi System.gc () nếu bạn biết rằng nó có ích. Điều đó có nghĩa là bạn đã kiểm tra kỹ lưỡng và đo lường hành vi của cả hai kịch bản trên nền tảng triển khai và bạn có thể chỉ ra điều đó có ích. Mặc dù vậy, hãy lưu ý rằng gc không dễ dự đoán - nó có thể giúp đỡ khi chạy và làm tổn thương người khác.


<Stro> Nhưng cũng từ Javadoc: _Khi điều khiển trả về từ lệnh gọi phương thức, máy ảo đã nỗ lực hết sức để tái chế tất cả các đối tượng bị loại bỏ, mà tôi thấy là một dạng bắt buộc hơn của những gì bạn đã đăng. > Xin lỗi, có một báo cáo lỗi về nó đang gây hiểu nhầm. Như đã biết rõ hơn, tác hại của việc gợi ý JVM là gì?
zneak

1
Tác hại là việc thu thập không đúng lúc có thể làm chậm rất nhiều. Gợi ý bạn đang đưa ra có lẽ là một điều xấu. Đối với nhận xét "nỗ lực tốt nhất", hãy thử và xem trong một công cụ như JConsole. Đôi khi, nhấp vào nút "Thực hiện GC" không làm gì cả
tom

Xin lỗi vì không đồng ý nhưng việc gọi System.gc () trong OpenJDK và bất cứ điều gì dựa trên nó (ví dụ HP) luôn dẫn đến chu trình thu gom rác. Trên thực tế, điều đó cũng đúng với việc triển khai J9 của IBM
Kirk

@Kirk - Không chính xác: google và đọc về -XX: -DisableExplicitGC.
Stephen C

0

Theo kinh nghiệm của tôi, sử dụng System.gc () thực sự là một hình thức tối ưu hóa dành riêng cho nền tảng (trong đó "nền tảng" là sự kết hợp của kiến ​​trúc phần cứng, phiên bản OS, JVM và có thể có nhiều tham số thời gian chạy hơn như RAM có sẵn), bởi vì hành vi của nó, trong khi có thể dự đoán gần đúng trên một nền tảng cụ thể, có thể (và sẽ) thay đổi đáng kể giữa các nền tảng.

Vâng, có rất tình huống mà System.gc () sẽ cải thiện hiệu suất (nhận thức). Ví dụ: nếu sự chậm trễ có thể chấp nhận được ở một số phần trong ứng dụng của bạn, nhưng không phải ở những phần khác (ví dụ về trò chơi được trích dẫn ở trên, nơi bạn muốn GC xảy ra khi bắt đầu cấp độ, không phải trong cấp độ).

Tuy nhiên, cho dù đó sẽ giúp đỡ hoặc tổn thương (hoặc không làm gì cả) là rất phụ thuộc vào nền tảng (như đã định nghĩa ở trên).

Vì vậy, tôi nghĩ rằng nó hợp lệ như là một tối ưu hóa cụ thể cho nền tảng cuối cùng (nghĩa là nếu tối ưu hóa hiệu suất khác là không đủ). Nhưng bạn không bao giờ nên gọi nó chỉ vì bạn tin rằng nó có thể giúp ích (không có điểm chuẩn cụ thể), bởi vì rất có thể nó sẽ không.


0
  1. Vì các đối tượng được phân bổ động bằng cách sử dụng toán tử mới,
    bạn có thể tự hỏi làm thế nào các đối tượng đó bị phá hủy và
    bộ nhớ của chúng được giải phóng để tái phân bổ sau này.

  2. Trong một số ngôn ngữ, chẳng hạn như C ++, các đối tượng được phân bổ động phải được giải phóng thủ công bằng cách sử dụng toán tử xóa.

  3. Java có một cách tiếp cận khác; nó tự động xử lý thỏa thuận cho bạn.
  4. Kỹ thuật thực hiện điều này được gọi là thu gom rác. Nó hoạt động như thế này: khi không có tham chiếu đến một đối tượng tồn tại, đối tượng đó được coi là không còn cần thiết và bộ nhớ bị chiếm bởi đối tượng có thể được lấy lại. Không có nhu cầu rõ ràng để phá hủy các đối tượng như trong C ++.
  5. Thu gom rác chỉ xảy ra lẻ tẻ (nếu có) trong quá trình thực hiện chương trình của bạn.
  6. Nó sẽ không xảy ra đơn giản vì một hoặc nhiều đối tượng tồn tại không còn được sử dụng.
  7. Hơn nữa, các triển khai thời gian chạy Java khác nhau sẽ có các cách tiếp cận khác nhau để thu gom rác, nhưng đối với hầu hết các phần, bạn không cần phải suy nghĩ về nó trong khi viết các chương trình của mình.
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.