Việc gán các đối tượng cho null trong Java có ảnh hưởng đến việc thu gom rác không?


85

Việc gán một tham chiếu đối tượng không sử dụng đến nulltrong Java có cải thiện quá trình thu gom rác theo bất kỳ cách nào có thể đo lường được không?

Kinh nghiệm của tôi với Java (và C #) đã dạy cho tôi rằng thường khó sử dụng máy ảo hoặc trình biên dịch JIT, nhưng tôi đã thấy đồng nghiệp sử dụng phương pháp này và tôi tò mò không biết đây có phải là một phương pháp hay để chọn lên hay một trong những mê tín lập trình voodoo đó?

Câu trả lời:


66

Điển hình là không.

Nhưng giống như tất cả mọi thứ: nó phụ thuộc. GC trong Java ngày nay RẤT tốt và mọi thứ sẽ được dọn dẹp rất nhanh sau khi không thể truy cập được nữa. Đây chỉ là sau khi để lại một phương thức cho các biến cục bộ và khi một cá thể lớp không còn được tham chiếu cho các trường.

Bạn chỉ cần null rõ ràng nếu bạn biết nó sẽ vẫn được tham chiếu nếu không. Ví dụ một mảng được giữ xung quanh. Bạn có thể muốn bỏ trống các phần tử riêng lẻ của mảng khi chúng không còn cần thiết nữa.

Ví dụ, mã này từ ArrayList:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

Ngoài ra, việc vô hiệu hóa một đối tượng một cách rõ ràng sẽ không làm cho một đối tượng được thu thập sớm hơn nếu nó chỉ ra khỏi phạm vi một cách tự nhiên miễn là không còn tham chiếu nào.

Cả hai:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

và:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

Có chức năng tương đương.


7
bạn cũng có thể muốn vô hiệu hóa các tham chiếu bên trong các phương thức có thể sử dụng nhiều bộ nhớ hoặc mất nhiều thời gian để thực thi, hoặc trong mã trong ứng dụng Swing có thể chạy lâu. Trong các phương thức ngắn hoặc các đối tượng tồn tại trong thời gian ngắn, nó không đáng có thêm mã khó hiểu.
John Gardner

1
Vậy có đúng không, khi chúng ta vô hiệu hóa một đối tượng, Bộ thu gom rác sẽ thu thập đối tượng đó ngay lập tức ??
Muhammad Babar

1
Không. Bộ thu gom rác được thực thi định kỳ để xem liệu có bất kỳ đối tượng nào mà không có biến tham chiếu nào trỏ tới hay không. Khi bạn đặt chúng thành null và sau đó gọi System.gc()thì nó sẽ bị xóa (miễn là không có tham chiếu nào khác trỏ đến đối tượng đó).
JavaTechnical

2
@JavaTechnical Như tôi biết không có gì đảm bảo rằng bằng cách gọi System.gc () quá trình thu gom rác sẽ bắt đầu.
Jack

12

Theo kinh nghiệm của tôi, thường xuyên hơn không, mọi người loại bỏ các tham chiếu vì hoang tưởng không phải là do cần thiết. Đây là hướng dẫn nhanh:

  1. Nếu đối tượng A tham chiếu đến đối tượng B bạn không còn cần tham chiếu này nữa đối tượng A không đủ điều kiện để thu gom rác thì bạn nên xóa trường một cách rõ ràng. Không cần phải hủy bỏ một trường nếu đối tượng bao quanh vẫn đang được thu gom rác. Việc loại bỏ các trường trong phương thức dispose () hầu như luôn vô dụng.

  2. Không cần phải hủy bỏ tham chiếu đối tượng được tạo trong một phương thức. Chúng sẽ được xóa tự động khi phương thức kết thúc. Ngoại lệ đối với quy tắc này là nếu bạn đang chạy trong một phương thức rất dài hoặc một số vòng lặp lớn và bạn cần đảm bảo rằng một số tham chiếu được xóa trước khi kết thúc phương thức. Một lần nữa, những trường hợp này là cực kỳ hiếm.

Tôi sẽ nói rằng phần lớn thời gian bạn sẽ không cần phải hủy bỏ các tham chiếu. Cố gắng vượt qua người thu gom rác là vô ích. Bạn sẽ chỉ nhận được mã không hiệu quả, không thể đọc được.


11

Bài báo hay là nỗi kinh hoàng về mã hóa ngày nay .

Cách hoạt động của GC là tìm kiếm các đối tượng không có bất kỳ con trỏ nào đến chúng, khu vực tìm kiếm của chúng là đống / ngăn xếp và bất kỳ không gian nào khác mà chúng có. Vì vậy, nếu bạn đặt một biến thành null, đối tượng thực tế bây giờ không được chỉ bởi bất kỳ ai, và do đó có thể là GC'd.

Nhưng vì GC có thể không chạy vào thời điểm chính xác đó, bạn có thể không thực sự mua cho mình bất cứ thứ gì. Nhưng nếu phương pháp của bạn khá dài (về thời gian thực hiện) thì nó có thể đáng giá vì bạn sẽ tăng cơ hội GC thu thập đối tượng đó.

Vấn đề cũng có thể phức tạp với việc tối ưu hóa mã, nếu bạn không bao giờ sử dụng biến sau khi bạn đặt nó thành null, thì sẽ là một cách tối ưu hóa an toàn để xóa dòng đặt giá trị thành null (một lệnh ít hơn để thực thi). Vì vậy, bạn có thể không thực sự nhận được bất kỳ cải thiện nào.

Vì vậy, tóm lại, , nó có thể giúp ích, nhưng nó sẽ không xác định .



Điểm tốt là tối ưu hóa an toàn loại bỏ dòng thiết lập tham chiếu thành null.
asgs

8

Ít nhất trong java, nó không phải là lập trình voodoo chút nào. Khi bạn tạo một đối tượng trong java bằng cách sử dụng một cái gì đó như

Foo bar = new Foo();

bạn làm hai việc: đầu tiên, bạn tạo một tham chiếu đến một đối tượng và thứ hai, bạn tạo chính đối tượng Foo. Miễn là tham chiếu đó hoặc tham chiếu khác tồn tại, đối tượng cụ thể không thể được gc'd. tuy nhiên, khi bạn gán null cho tham chiếu đó ...

bar = null ;

và giả sử không có gì khác có tham chiếu đến đối tượng, nó được giải phóng và có sẵn cho gc vào lần tiếp theo trình thu gom rác đi qua.


12
Nhưng đi ra khỏi phạm vi sẽ làm điều tương tự mà không cần thêm dòng mã. Nếu nó cục bộ cho một phương thức, thì không cần. Sau khi bạn rời khỏi phương thức, đối tượng sẽ đủ điều kiện cho GC.
duffymo

2
Tốt. bây giờ, đối với một bài kiểm tra pop, hãy xây dựng một ví dụ về mã mà nó KHÔNG đủ. Gợi ý: chúng tồn tại.
Charlie Martin

7

Nó phụ thuộc.

Nói chung ngắn gọn hơn, bạn giữ các tham chiếu đến các đối tượng của mình, chúng sẽ được thu thập nhanh hơn.

Nếu phương thức của bạn mất 2 giây để thực thi và bạn không cần một đối tượng nữa sau một giây thực thi phương thức, bạn nên xóa mọi tham chiếu đến nó. Nếu GC thấy rằng sau một giây, đối tượng của bạn vẫn được tham chiếu, lần sau, nó có thể kiểm tra nó sau một phút hoặc lâu hơn.

Dù sao, đặt tất cả các tham chiếu thành null theo mặc định đối với tôi là tối ưu hóa quá sớm và không ai nên làm điều đó trừ khi trong một số trường hợp hiếm hoi cụ thể khi nó làm giảm khả năng tích hợp bộ nhớ một cách đáng kể.


6

Việc thiết lập rõ ràng một tham chiếu đến null thay vì chỉ để biến ra khỏi phạm vi, không giúp ích cho bộ thu gom rác, trừ khi đối tượng được giữ rất lớn, nơi đặt nó thành null ngay sau khi bạn hoàn thành là một ý kiến ​​hay.

Nói chung, thiết lập các tham chiếu thành null, nghĩa là đối với NGƯỜI ĐỌC mã mà đối tượng này đã hoàn thành và không nên quan tâm đến bất kỳ điều gì nữa.

Có thể đạt được hiệu quả tương tự bằng cách giới thiệu phạm vi hẹp hơn bằng cách đặt thêm một bộ niềng răng

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

điều này cho phép bigThing được thu gom rác ngay sau khi rời khỏi các dấu ngoặc nhọn lồng nhau.


Thay thế tốt. Điều này tránh đặt biến rõ ràng thành null cảnh báo lint một số IDE hiển thị về việc gán null đó không được sử dụng.
jk7

5
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

Chương trình trên ném OutOfMemoryError. Nếu bạn bỏ ghi chú data = null;, OutOfMemoryErrornó được giải quyết. Cách tốt nhất là đặt biến không sử dụng thành null


Đó là, imo, một bước đột phá vững chắc vào thế giới của những lập luận người rơm. Chỉ vì bạn CÓ THỂ tạo ra một tình huống trong đó việc đặt biến thành null sẽ giải quyết được vấn đề trong trường hợp cụ thể đó (và tôi không tin rằng bạn đã làm như vậy) không có nghĩa là đặt biến thành null là một ý tưởng hay trong mọi trường hợp. Việc thêm mã để đặt mọi biến bạn không sử dụng nữa thành null, ngay cả khi chúng sẽ vượt ra khỏi phạm vi ngay sau đó, chỉ làm tăng thêm mã và làm cho mã ít bảo trì hơn.
RHSeeger

Tại sao mảng byte [] rác không được thu thập tại thời điểm dữ liệu biến nằm ngoài phạm vi?
Grav

2
@Grav: đi ra khỏi phạm vi không đảm bảo rằng bộ thu gom rác sẽ thu thập các đối tượng cục bộ. Tuy nhiên, việc đặt nó thành null sẽ ngăn ngoại lệ OutOfMemory, vì bạn được đảm bảo rằng GC sẽ chạy trước khi dùng đến việc ném một ngoại lệ OutOfMemory và nếu đối tượng được đặt thành null thì nó sẽ có thể được thu thập.
Stefanos T.


4

Một lần tôi đang làm việc trên một ứng dụng hội nghị truyền hình và nhận thấy sự khác biệt rất lớn về hiệu suất khi tôi dành thời gian để vô hiệu hóa các tham chiếu ngay khi tôi không cần đối tượng nữa. Đó là vào năm 2003-2004 và tôi chỉ có thể tưởng tượng GC đã trở nên thông minh hơn kể từ đó. Trong trường hợp của tôi, tôi có hàng trăm đối tượng đến và đi ra khỏi phạm vi mỗi giây, vì vậy tôi nhận thấy GC khi nó khởi động theo định kỳ. Tuy nhiên, sau khi tôi chỉ ra các đối tượng rỗng, GC đã ngừng tạm dừng ứng dụng của tôi.

Vì vậy, nó phụ thuộc vào những gì bạn đang làm ...


2

Đúng.

Từ "The Pragmatic Programmer" p.292:

Bằng cách đặt tham chiếu đến NULL, bạn giảm số lượng con trỏ đến đối tượng đi một ... (điều này sẽ cho phép bộ thu gom rác loại bỏ nó)


Tôi đoán điều này chỉ áp dụng khi thuật toán số lượng tham chiếu. đang được sử dụng hay cách khác cũng được?
Nrj

3
Lời khuyên này đã lỗi thời đối với bất kỳ người thu gom rác hiện đại nào; và việc đếm tham chiếu GC có những vấn đề đáng kể, chẳng hạn như tham chiếu vòng.
Lawrence Dol,

Nó cũng sẽ đúng trong một đánh dấu và quét GC; có lẽ ở tất cả những người khác. Việc bạn có ít tham chiếu hơn không có nghĩa là tham chiếu đó được tính. Câu trả lời của tweakt giải thích điều này. Tốt hơn là giảm phạm vi hơn là đặt thành NULL.

1

Tôi giả sử OP đang đề cập đến những thứ như thế này:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

Trong trường hợp này, chẳng phải VM sẽ đánh dấu chúng cho GC ngay khi chúng rời khỏi phạm vi sao?

Hoặc, từ một góc độ khác, việc đặt các mục thành null rõ ràng có khiến chúng nhận được GC trước khi chúng xảy ra nếu chúng chỉ vượt ra khỏi phạm vi? Nếu vậy, VM có thể dành thời gian GC'ing đối tượng khi bộ nhớ không cần thiết, điều này thực sự sẽ gây ra hiệu suất kém hơn khi sử dụng CPU một cách khôn ngoan vì nó sẽ được GC sớm hơn.


Thời gian GC chạy là không xác định. Tôi không tin rằng việc đặt một đối tượng sẽ nullảnh hưởng đến hành vi GC.
harto 25/09/09

0

" Nó phụ thuộc "

Tôi không biết về Java nhưng trong .net (C #, VB.net ...) thường không bắt buộc phải gán null khi bạn không còn yêu cầu một đối tượng nữa.

Tuy nhiên lưu ý rằng nó "thường không bắt buộc".

Bằng cách phân tích mã của bạn, trình biên dịch .net sẽ đánh giá tốt thời gian tồn tại của biến ... để cho biết chính xác khi nào đối tượng không được sử dụng nữa. Vì vậy, nếu bạn viết obj = null, nó thực sự có thể trông như thể obj vẫn đang được sử dụng ... trong trường hợp này, việc gán null sẽ phản tác dụng.

Có một số trường hợp thực sự có thể hữu ích khi gán giá trị rỗng. Một ví dụ là bạn có một mã lớn chạy trong thời gian dài hoặc một phương thức đang chạy trong một chuỗi khác hoặc một số vòng lặp. Trong những trường hợp như vậy, nó có thể hữu ích khi gán null để GC dễ dàng biết nó không được sử dụng nữa.

Không có quy tắc cứng & nhanh cho việc này. Đi qua nơi trên null-gán trong mã của bạn và chạy một trình biên dịch để xem nó có giúp ích gì không. Hầu hết có thể bạn không thấy một lợi ích.

Nếu đó là mã .net mà bạn đang cố gắng tối ưu hóa, thì kinh nghiệm của tôi là chăm sóc tốt với các phương pháp Dispose và Finalize thực sự có lợi hơn là bận tâm về null.

Một số tài liệu tham khảo về chủ đề:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx


0

Ngay cả khi việc vô hiệu hóa tham chiếu có hiệu quả hơn một chút, liệu có đáng để bạn phải tiêu diệt mã của mình với những nội dung vô hiệu xấu xí này không? Chúng sẽ chỉ lộn xộn và che khuất mã ý định chứa chúng.

Cơ sở mã hiếm của nó không có ứng cử viên nào tốt hơn để tối ưu hóa hơn là cố gắng vượt qua trình thu gom rác (hiếm hơn vẫn là những nhà phát triển thành công trong việc vượt qua nó). Thay vào đó, nỗ lực của bạn rất có thể sẽ được dành cho nơi khác, bỏ qua trình phân tích cú pháp Xml thô lỗ đó hoặc tìm một số cơ hội để tính toán vào bộ nhớ cache. Những tối ưu hóa này sẽ dễ dàng hơn để định lượng và không yêu cầu bạn làm bẩn cơ sở mã của mình bằng nhiễu.


0

Trong quá trình thực thi chương trình của bạn trong tương lai, giá trị của một số thành viên dữ liệu sẽ được sử dụng để máy tính tạo ra một đầu ra có thể nhìn thấy bên ngoài chương trình. Những người khác có thể được sử dụng hoặc không, tùy thuộc vào đầu vào (Và không thể đoán trước) trong tương lai của chương trình. Các thành viên dữ liệu khác có thể được đảm bảo không được sử dụng. Tất cả tài nguyên, bao gồm cả bộ nhớ, được phân bổ cho những dữ liệu không sử dụng đó đều bị lãng phí. Công việc của bộ thu gom rác (GC) là loại bỏ bộ nhớ lãng phí đó. Sẽ thật tai hại nếu GC loại bỏ thứ gì đó cần thiết, do đó, thuật toán được sử dụng có thể bảo thủ, giữ lại nhiều hơn mức tối thiểu nghiêm ngặt. Nó có thể sử dụng các tối ưu hóa heuristic để cải thiện tốc độ, với chi phí là giữ lại một số mục không thực sự cần thiết. Có nhiều thuật toán tiềm năng mà GC có thể sử dụng. Do đó, có thể những thay đổi bạn thực hiện đối với chương trình của mình, và điều này không ảnh hưởng đến tính đúng đắn của chương trình của bạn, tuy nhiên có thể ảnh hưởng đến hoạt động của GC, làm cho nó chạy nhanh hơn để thực hiện cùng một công việc hoặc xác định sớm hơn các mục không sử dụng. Vì vậy, loại thay đổi này, đặt một tham chiếu đối tượng không sử dụng được thànhnull, về lý thuyết không phải lúc nào cũng là voodoo.

Nó có phải là voodoo? Có một số phần của mã thư viện Java thực hiện điều này. Những người viết mã đó giỏi hơn nhiều so với những lập trình viên bình thường và có thể biết hoặc hợp tác với những lập trình viên biết chi tiết về việc triển khai bộ thu gom rác. Vì vậy, cho thấy có đôi khi một lợi ích.


0

Như bạn đã nói có các tối ưu hóa, tức là JVM biết vị trí khi biến được sử dụng lần cuối và đối tượng được tham chiếu bởi nó có thể được GCed ngay sau điểm cuối cùng này (vẫn đang thực thi trong phạm vi hiện tại). Vì vậy, việc loại bỏ tham chiếu trong hầu hết các trường hợp không giúp ích gì cho GC.

Nhưng nó có thể hữu ích để tránh vấn đề "chế độ tân học" (hoặc "rác trôi nổi") ( đọc thêm tại đây hoặc xem video ). Vấn đề tồn tại bởi vì heap được chia thành các thế hệ Già và Trẻ và có các cơ chế GC khác nhau được áp dụng: GC nhỏ (nhanh và thường xảy ra để làm sạch gen trẻ) và Gc chính (khiến thời gian tạm dừng lâu hơn để làm sạch gen cũ). "Chủ nghĩa thận trọng" không cho phép thu gom rác ở gen Trẻ nếu nó được tham chiếu bởi rác đã thuộc quyền sở hữu của gen Cũ.

Đây là 'bệnh lý' vì BẤT KỲ nút nào được quảng bá sẽ dẫn đến việc quảng bá TẤT CẢ các nút sau cho đến khi GC giải quyết được vấn đề.

Để tránh chế độ gia đình trị, bạn nên loại bỏ các tham chiếu khỏi một đối tượng được cho là sẽ bị xóa. Bạn có thể thấy kỹ thuật này được áp dụng trong các lớp JDK: LinkedListLinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
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.