Bộ sưu tập rác Java hoạt động như thế nào với các tài liệu tham khảo thông tư?


161

Theo hiểu biết của tôi, bộ sưu tập rác trong Java sẽ dọn sạch một số đối tượng nếu không có gì khác là 'trỏ' vào đối tượng đó.

Câu hỏi của tôi là, điều gì xảy ra nếu chúng ta có một cái gì đó như thế này:

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code

a, bcnên được thu gom rác, nhưng tất cả chúng đều được các đối tượng khác tham chiếu.

Làm thế nào để bộ sưu tập rác Java xử lý vấn đề này? (hoặc chỉ đơn giản là một bộ nhớ cống?)


1
Xem: stackoverflow.com/questions/407855/ , cụ thể là câu trả lời thứ hai từ @gnud.
Seth

Câu trả lời:


161

GC của Java coi các đối tượng là "rác" nếu chúng không thể truy cập được thông qua chuỗi bắt đầu từ gốc bộ sưu tập rác, vì vậy các đối tượng này sẽ được thu thập. Mặc dù các đối tượng có thể chỉ vào nhau để tạo thành một chu kỳ, chúng vẫn là rác nếu chúng bị cắt khỏi gốc.

Xem phần về các đối tượng không thể truy cập trong Phụ lục A: Sự thật về Bộ sưu tập rác trong Hiệu suất nền tảng Java: Chiến lược và Chiến thuật để biết chi tiết về tin đồn.


14
Bạn có một tài liệu tham khảo cho điều đó? Thật khó để kiểm tra nó.
tangens

5
Tôi đã thêm một tài liệu tham khảo. Bạn cũng có thể ghi đè phương thức quyết toán () của đối tượng để tìm hiểu khi nào nó được thu thập (mặc dù đó là điều duy nhất tôi khuyên bạn nên sử dụng quyết định () cho).
Bill Lizard

1
Chỉ cần làm rõ nhận xét cuối cùng đó ... đặt một câu lệnh in gỡ lỗi trong phương thức hoàn thiện in ra một id duy nhất cho đối tượng. Bạn sẽ có thể thấy tất cả các đối tượng tham chiếu lẫn nhau được thu thập.
Bill Lizard

4
"... đủ thông minh để nhận ra ..." nghe có vẻ khó hiểu. GC không phải nhận ra các chu kỳ - chúng không thể truy cập được, do đó rác
Alexander Malakhov

86
@tangens "Bạn có tài liệu tham khảo cho điều đó không?" trong một cuộc thảo luận về thu gom rác. Tốt. Trừng phạt. Không bao giờ.
Michał Kosmulski

139

có Trình thu gom rác Java xử lý tham chiếu vòng tròn!

How?

Có những đối tượng đặc biệt được gọi là rễ thu gom rác (rễ GC). Chúng luôn có thể truy cập và bất kỳ đối tượng nào có chúng ở gốc của nó.

Một ứng dụng Java đơn giản có các gốc GC sau:

  1. Biến cục bộ trong phương thức chính
  2. Chủ đề chính
  3. Biến tĩnh của lớp chính

nhập mô tả hình ảnh ở đây

Để xác định các đối tượng nào không còn được sử dụng, JVM chạy liên tục cái được gọi là thuật toán đánh dấu và quét . Nó hoạt động như sau

  1. Thuật toán duyệt qua tất cả các tham chiếu đối tượng, bắt đầu bằng các gốc GC và đánh dấu mọi đối tượng được tìm thấy là còn sống.
  2. Tất cả bộ nhớ heap không bị chiếm bởi các đối tượng được đánh dấu sẽ được thu hồi. Nó chỉ đơn giản được đánh dấu là miễn phí, về cơ bản không có các đối tượng không sử dụng.

Vì vậy, nếu bất kỳ đối tượng nào không thể truy cập được từ các gốc GC (ngay cả khi nó tự tham chiếu hoặc tham chiếu theo chu kỳ), nó sẽ phải chịu thu gom rác.

Tất nhiên, điều này đôi khi có thể dẫn đến rò rỉ bộ nhớ nếu lập trình viên quên không đăng ký một đối tượng.

nhập mô tả hình ảnh ở đây

Nguồn: Quản lý bộ nhớ Java


3
Giải thích hoàn hảo! Cảm ơn! :)
Jovan Perovic

Cảm ơn đã liên kết cuốn sách đó. Nó chứa đầy thông tin tuyệt vời về điều này và các chủ đề phát triển Java khác!
Droj

14
Trong hình ảnh cuối cùng, có một đối tượng không thể tiếp cận nhưng trong phần đối tượng có thể tiếp cận.
La VloZ Merrill

13

Trình thu gom rác bắt đầu từ một số vị trí "gốc" luôn được coi là "có thể truy cập", chẳng hạn như các thanh ghi CPU, ngăn xếp và các biến toàn cục. Nó hoạt động bằng cách tìm bất kỳ con trỏ nào trong các khu vực đó và tìm đệ quy mọi thứ chúng chỉ vào. Một khi nó tìm thấy tất cả, mọi thứ khác là rác.

Tất nhiên, có khá nhiều biến thể, chủ yếu là vì tốc độ. Ví dụ, hầu hết các trình thu gom rác hiện đại là "thế hệ", nghĩa là chúng phân chia các đối tượng thành các thế hệ và khi một đối tượng già đi, trình thu gom rác sẽ kéo dài hơn và lâu hơn giữa các lần mà nó cố gắng tìm hiểu xem đối tượng đó có còn hợp lệ hay không - nó chỉ bắt đầu cho rằng nếu nó đã sống lâu, rất có thể nó sẽ tiếp tục sống lâu hơn nữa.

Tuy nhiên, ý tưởng cơ bản vẫn giống nhau: tất cả dựa trên việc bắt đầu từ một số tập hợp gốc mà nó được cho là vẫn có thể được sử dụng, và sau đó theo đuổi tất cả các con trỏ để tìm những gì khác có thể được sử dụng.

Thú vị sang một bên: có thể mọi người thường ngạc nhiên về mức độ tương tự giữa phần này của trình thu gom rác và mã cho các đối tượng đầm lầy cho những thứ như các cuộc gọi thủ tục từ xa. Trong mỗi trường hợp, bạn bắt đầu từ một số đối tượng gốc và đuổi theo con trỏ để tìm tất cả các đối tượng khác đề cập đến ...


Những gì bạn đang mô tả là một nhà sưu tầm truy tìm. Có những loại người thu gom khác. Quan tâm đặc biệt cho cuộc thảo luận này là tài liệu tham khảo nhà sưu tập đếm, mà làm có xu hướng gặp rắc rối với chu kỳ.
Jörg W Mittag

@ Jorg W Mittag: Chắc chắn là đúng - mặc dù tôi không biết về một JVM (hiện tại hợp lý) sử dụng tính năng tham chiếu, nên có vẻ như không chắc chắn (ít nhất là với tôi) rằng nó có nhiều khác biệt so với câu hỏi ban đầu.
Jerry Coffin

@ Jorg W Mittag: Ít nhất theo mặc định, tôi tin rằng Jike RVM hiện đang sử dụng trình thu thập Immix, một trình thu thập theo dõi khu vực (mặc dù nó cũng sử dụng tính năng tham chiếu). Tôi không chắc liệu bạn đang đề cập đến việc đếm tham chiếu đó hay một nhà sưu tập khác sử dụng đếm tham chiếu mà không theo dõi (tôi đoán là sau này, vì tôi chưa bao giờ nghe nói về việc Immix gọi "tái chế").
Jerry Coffin

Tôi đã nhầm lẫn một chút: Recycler được (được?) Thực hiện trong Jalapeno, thuật toán mà tôi đã nghĩ đến, đó là (được?) Thực hiện trong Jike là Đếm Tham chiếu Ul thầm . Tất nhiên, Atlhough nói rằng Jike sử dụng cái này hoặc bộ thu gom rác là khá vô ích, vì Jike và đặc biệt là MMtk được thiết kế đặc biệt để nhanh chóng phát triển và thử nghiệm các bộ thu gom rác khác nhau trong cùng một JVM.
Jörg W Mittag

2
Đếm tham chiếu Ul thầm được thiết kế vào năm 2003 bởi cùng những người đã thiết kế Immix vào năm 2007, vì vậy tôi đoán rằng cái sau có lẽ thay thế cái trước. URC được thiết kế đặc biệt để có thể kết hợp với các chiến lược khác, và trên thực tế, bài viết của URC đề cập rõ ràng rằng URC chỉ là bước đệm hướng tới một nhà sưu tập kết hợp các lợi thế của việc truy tìm và đếm tham chiếu. Tôi đoán Immix là người sưu tầm đó. Dù sao, Recycler là một trình thu thập tham chiếu thuần túy , dù sao cũng có thể phát hiện và thu thập các chu kỳ: WWW.Research.IBM.Com/people/d/dfb/recycler.html
Jörg W Mittag

13

Bạn nói đúng. Hình thức cụ thể của bộ sưu tập rác mà bạn mô tả được gọi là " đếm tham chiếu ". Cách thức hoạt động (về mặt khái niệm, ít nhất, hầu hết các triển khai hiện đại về đếm tham chiếu thực tế được thực hiện hoàn toàn khác nhau) trong trường hợp đơn giản nhất, trông như thế này:

  • Bất cứ khi nào một tham chiếu đến một đối tượng được thêm vào (ví dụ: nó được gán cho một biến hoặc một trường, được truyền cho phương thức, v.v.), số tham chiếu của nó được tăng thêm 1
  • Bất cứ khi nào một tham chiếu đến một đối tượng bị xóa (phương thức trả về, biến đi ra khỏi phạm vi, trường được gán lại cho một đối tượng khác hoặc đối tượng chứa trường được thu gom rác), số lượng tham chiếu giảm đi 1
  • ngay khi số tham chiếu chạm 0, không còn tham chiếu đến đối tượng nữa, điều đó có nghĩa là không ai có thể sử dụng nó nữa, do đó nó là rác và có thể được thu thập

Và chiến lược đơn giản này có chính xác vấn đề bạn giải mã: nếu A tham chiếu B và B tham chiếu A, thì cả hai số tham chiếu của chúng không bao giờ có thể nhỏ hơn 1, có nghĩa là chúng sẽ không bao giờ được thu thập.

Có bốn cách để giải quyết vấn đề này:

  1. Bỏ mặc nó. Nếu bạn có đủ bộ nhớ, các chu kỳ của bạn nhỏ và không thường xuyên và thời gian chạy của bạn ngắn, có thể bạn có thể thoát khỏi chỉ với việc không thu thập các chu kỳ. Hãy nghĩ về một trình thông dịch kịch bản lệnh shell: các kịch bản shell thường chỉ chạy trong vài giây và không phân bổ nhiều bộ nhớ.
  2. Kết hợp bộ đếm rác tham chiếu của bạn với bộ thu gom rác khác không có vấn đề với chu kỳ. CPython thực hiện điều này, ví dụ: trình thu gom rác chính trong CPython là trình thu gom đếm tham chiếu, nhưng theo thời gian, một trình thu gom rác truy tìm được chạy để thu thập các chu kỳ.
  3. Phát hiện các chu kỳ. Thật không may, phát hiện các chu kỳ trong biểu đồ là một hoạt động khá tốn kém. Đặc biệt, nó đòi hỏi khá nhiều chi phí tương tự như một bộ sưu tập theo dõi, vì vậy bạn cũng có thể sử dụng một trong số đó.
  4. Đừng thực hiện thuật toán theo cách ngây thơ mà bạn và tôi sẽ làm: từ những năm 1970, đã có nhiều thuật toán khá thú vị được phát triển kết hợp phát hiện chu kỳ và đếm tham chiếu trong một thao tác theo cách thông minh rẻ hơn đáng kể so với thực hiện chúng cả hai cách riêng biệt hoặc làm một bộ sưu tập theo dõi.

Nhân tiện, cách chính khác để thực hiện một trình thu gom rác (và tôi đã gợi ý rằng một vài lần ở trên), là truy tìm . Một bộ sưu tập truy tìm dựa trên khái niệm khả năng tiếp cận . Bạn bắt đầu với một số tập hợp gốc mà bạn biết là luôn có thể truy cập (ví dụ: hằng toàn cục hoặc Objectlớp, phạm vi từ vựng hiện tại, khung ngăn xếp hiện tại) và từ đó bạn theo dõi tất cả các đối tượng có thể truy cập từ tập hợp gốc, sau đó tất cả các đối tượng có thể truy cập từ các đối tượng có thể truy cập từ tập gốc và cứ thế, cho đến khi bạn có bao đóng bắc cầu. Tất cả những gì không có trong đó là rác.

Vì một chu trình chỉ có thể truy cập trong chính nó, nhưng không thể truy cập được từ tập gốc, nên nó sẽ được thu thập.


1
Vì câu hỏi là dành riêng cho Java, tôi nghĩ rằng đáng để đề cập rằng Java không sử dụng tính năng tham chiếu và do đó vấn đề không tồn tại. Ngoài ra liên kết đến wikipedia sẽ hữu ích như "đọc thêm". Nếu không thì tổng quan tuyệt vời!
Alexander Malakhov

Tôi vừa đọc bình luận của bạn về bài đăng của Jerry Coffin, vì vậy bây giờ tôi không chắc lắm :)
Alexander Malakhov

8

Các Java Java không thực sự hành xử như bạn mô tả. Chính xác hơn để nói rằng họ bắt đầu từ một tập hợp các đối tượng cơ bản, thường được gọi là "gốc GC" và sẽ thu thập bất kỳ đối tượng nào không thể truy cập từ gốc.
Rễ GC bao gồm những thứ như:

  • biến tĩnh
  • các biến cục bộ (bao gồm tất cả các tham chiếu 'this' hiện hành) hiện có trong ngăn xếp của một luồng đang chạy

Vì vậy, trong trường hợp của bạn, một khi các biến cục bộ a, b và c vượt ra khỏi phạm vi ở cuối phương thức của bạn, sẽ không có gốc GC nào chứa, trực tiếp hoặc gián tiếp, tham chiếu đến bất kỳ nút nào trong ba nút của bạn và họ sẽ đủ điều kiện để thu gom rác.

Liên kết của TofuBERer có nhiều chi tiết hơn nếu bạn muốn.


"... hiện đang nằm trong ngăn xếp của một luồng đang chạy ..." không phải nó đang quét các ngăn xếp của tất cả các luồng để không làm hỏng dữ liệu của các luồng khác?
Alexander Malakhov

6

Bài viết này (không còn có sẵn) đi sâu về trình thu gom rác (về mặt khái niệm ... có một số triển khai). Phần có liên quan đến bài đăng của bạn là "A.3.4 Không thể truy cập":

A.3.4 Không thể truy cập Một đối tượng đi vào trạng thái không thể truy cập khi không còn tham chiếu mạnh đến nó. Khi một đối tượng không thể truy cập, nó là một ứng cử viên cho bộ sưu tập. Lưu ý từ ngữ: Chỉ vì một đối tượng là ứng cử viên cho bộ sưu tập không có nghĩa là nó sẽ được thu thập ngay lập tức. JVM có thể trì hoãn việc thu thập cho đến khi có nhu cầu ngay lập tức cho bộ nhớ được sử dụng bởi đối tượng.



1
các liên kết không còn nữa
Titus

1

Bộ sưu tập rác thường không có nghĩa là "làm sạch một số đối tượng nếu không có gì khác là" chỉ "vào đối tượng đó" (đó là tính tham chiếu). Bộ sưu tập rác đại khái có nghĩa là tìm các đối tượng không thể đạt được từ chương trình.

Vì vậy, trong ví dụ của bạn, sau khi a, b và c vượt ra khỏi phạm vi, chúng có thể được thu thập bởi GC, vì bạn không thể truy cập các đối tượng này nữa.


"Bộ sưu tập rác đại khái có nghĩa là tìm các đối tượng không thể đạt được từ chương trình". Trong hầu hết các thuật toán GC, nó thực sự là cách khác. Bạn bắt đầu với các gốc GC và xem những gì bạn có thể tìm thấy, phần còn lại được coi là rác không được kiểm soát.
Fredrik

1
Đếm tham chiếu một trong hai chiến lược thực hiện chính cho việc thu gom rác. (Cái kia là truy tìm.)
Jörg W Mittag

3
@ Jörg: Hầu hết thời gian ngày nay, khi mọi người nói về người thu gom rác, họ đang đề cập đến người thu gom dựa trên một số loại thuật toán quét của mark'n. Việc đếm tham chiếu thường là những gì bạn đang mắc kẹt nếu bạn không có công cụ thu gom rác. Đúng là việc đếm ref có ý nghĩa là một chiến lược thu gom rác nhưng hầu như không có gc nào tồn tại ngày nay được xây dựng trên nó nên nói rằng đó là một chiến lược gc sẽ khiến mọi người nhầm lẫn bởi vì trong thực tế, nó không còn là một gc nữa chiến lược nhưng một cách khác để quản lý bộ nhớ.
Fredrik

1

Bill đã trả lời câu hỏi của bạn trực tiếp. Như Amnon đã nói, định nghĩa của bạn về thu gom rác chỉ là tính tham khảo. Tôi chỉ muốn thêm rằng các thuật toán thậm chí rất đơn giản như đánh dấu và quét và sao chép bộ sưu tập dễ dàng xử lý các tham chiếu vòng tròn. Vì vậy, không có gì kỳ diệu về nó!

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.