2012-01-11 28 views
12

Tôi đã đọc "CLR qua C#" và có vẻ như trong ví dụ này, đối tượng ban đầu được gán cho 'obj' sẽ đủ điều kiện cho Bộ sưu tập rác sau khi thực hiện dòng 1, chứ không phải sau dòng 2.Tuổi thọ của đối tượng trong Java vs .Net

void Foo() 
{ 
    Object obj = new Object(); 
    obj = null; 
} 

Đó là do tuổi thọ biến cục bộ được xác định không theo phạm vi mà nó đã được xác định, nhưng lần cuối bạn đọc nó.

Câu hỏi của tôi là: Java thì sao? Tôi đã viết chương trình này để kiểm tra hành vi như vậy, và có vẻ như đối tượng vẫn còn sống. Tôi không nghĩ rằng nó có thể cho JVM để hạn chế tuổi thọ biến trong khi giải thích bytecode, vì vậy tôi đã cố gắng chạy chương trình với 'java -Xcomp' để buộc biên dịch phương pháp, nhưng 'finalize' không được gọi là anyway. Có vẻ như điều đó không đúng đối với Java, nhưng tôi hy vọng tôi có thể có được câu trả lời chính xác hơn ở đây. Ngoài ra, về máy ảo Dalvik của Android thì sao?

class TestProgram { 

    public static void main(String[] args) { 
     TestProgram ref = new TestProgram(); 
     System.gc(); 
    } 

    @Override 
    protected void finalize() { 
     System.out.println("finalized"); 
    } 
} 

Added: Jeffrey Richter cho ví dụ mã trong "CLR thông qua C#", một cái gì đó như thế này:

public static void Main (string[] args) 
{ 
    var timer = new Timer(TimerCallback, null, 0, 1000); // call every second 
    Console.ReadLine(); 
} 

public static void TimerCallback(Object o) 
{ 
    Console.WriteLine("Callback!"); 
    GC.Collect(); 
} 

TimerCallback gọi là một lần duy nhất trên MS Net nếu các dự án mục tiêu là 'phát hành' (timer bị hủy sau cuộc gọi GC.Collect()), và được gọi mỗi giây nếu mục tiêu là 'Debug' (biến tuổi thọ tăng lên vì lập trình viên có thể thử truy cập đối tượng bằng trình gỡ lỗi). Nhưng trên gọi lại Mono được gọi là mỗi giây không có vấn đề làm thế nào bạn biên dịch nó. Có vẻ như các cửa sổ triển khai 'Timer' của Mono tham chiếu đến cá thể ở đâu đó trong nhóm luồng. Triển khai MS không làm điều này.

+10

Tôi đã không tìm thấy điều này trong thông số Java trước đây. Lưu ý rằng .NET có thể thậm chí còn điên hơn bạn có thể mong đợi - có thể cho một cá thể được thu thập * trong khi một phương thức thể hiện đang được thực thi * nếu CLR biết rằng không có biến nào khác được gọi. –

Trả lời

4

Lưu ý rằng chỉ vì một đối tượng có thể thu thập, không có nghĩa là nó sẽ thực sự được thu thập bất kỳ điểm nào - vì vậy phương pháp của bạn có thể cho âm sai. Nếu phương thức finalize của đối tượng được gọi là bạn chắc chắn có thể nói rằng nó không thể truy cập được, nhưng nếu phương thức này không được gọi là bạn không thể suy luận một cách hợp lý bất kỳ thứ gì. Như với hầu hết các câu hỏi liên quan đến GC, tính không xác định của bộ thu gom rác làm cho việc kiểm tra/bảo đảm khó khăn về chính xác những gì nó sẽ làm.

Về chủ đề của reachability/năng thu hồi, JLS nói (12.6.1):

Một thể truy cập đối tượng là bất kỳ đối tượng có thể được truy cập trong bất kỳ tính toán tiếp tục tiềm năng từ bất kỳ thread sống. Tối ưu hóa các phép biến đổi của một chương trình có thể được thiết kế để giảm số lượng các đối tượng có thể truy cập được ít hơn các đối tượng được xem là ngây thơ. Ví dụ, một trình biên dịch hoặc trình tạo mã có thể chọn để đặt một biến hoặc tham số sẽ không còn được sử dụng để null để làm cho việc lưu trữ cho một đối tượng như vậy có khả năng có thể lấy lại sớm hơn.

Ít hay chính xác những gì bạn mong đợi - Tôi nghĩ đoạn trên là đẳng cấu với "đối tượng không thể truy cập được, bạn chắc chắn sẽ không sử dụng nó nữa".

Quay trở lại tình huống ban đầu của bạn, bạn có thể nghĩ ra bất kỳ phân nhánh thực tế nào giữa đối tượng được coi là không thể truy cập được sau dòng 1 trái với dòng 2 không? Phản ứng ban đầu của tôi là không có gì, và nếu bạn bằng cách nào đó tìm ra một tình huống như vậy, nó có thể là một dấu hiệu của mã xấu/xoắn khiến cho VM phải vật lộn hơn là một điểm yếu vốn có trong ngôn ngữ.

Mặc dù tôi đang mở để phản đối.


Chỉnh sửa: Cảm ơn ví dụ thú vị.

Tôi đồng ý với đánh giá của bạn và xem bạn sẽ đi đâu, mặc dù vấn đề có thể nhiều hơn là chế độ gỡ lỗi đang thay đổi tinh tế ngữ nghĩa của mã của bạn.

Trong mã được viết, bạn chỉ định Timer cho biến cục bộ mà sau đó không được đọc trong phạm vi của nó. Ngay cả phân tích thoát hiểm nhỏ nhất có thể tiết lộ rằng biến số timer không được sử dụng ở bất kỳ nơi nào khác trong phương pháp main và do đó có thể được elided. Do đó tôi nghĩ rằng dòng đầu tiên của bạn có thể được coi là chính xác tương đương với chỉ gọi các nhà xây dựng trực tiếp:

public static void Main (string[] args) 
{ 
    new Timer(TimerCallback, null, 0, 1000); // call every second 
    ... 

Trong trường hợp sau này rõ ràng là mới được tạo ra Timer đối tượng là không thể truy cập ngay lập tức sau khi thi công (giả định rằng nó doesn' t làm bất cứ điều gì lén lút như thêm chính nó vào các lĩnh vực tĩnh, vv trong constructor của nó); và do đó nó sẽ được thu thập ngay sau khi GC đã tròn cho nó.

Bây giờ trong trường hợp gỡ lỗi, mọi thứ khác nhau một cách tinh tế, vì lý do bạn đã đề cập đó là nhà phát triển có thể muốn kiểm tra trạng thái của các biến cục bộ sau này trong phương thức. Do đó trình biên dịch (và trình biên dịch JIT) không thể tối ưu hóa chúng đi; nó như thể có một truy cập của biến ở cuối phương thức, ngăn chặn việc thu thập cho đến thời điểm đó.

Mặc dù vậy, tôi không nghĩ rằng điều này thực sự làm thay đổi ngữ nghĩa. Bản chất của GC là bộ sưu tập hiếm khi được bảo đảm (trong Java ít nhất, sự đảm bảo duy nhất bạn nhận được là nếu một lỗi OutOfMemoryError được ném thì mọi thứ được coi là không thể truy cập được GCed ngay trước đó). Trong thực tế, giả định rằng bạn có đủ không gian lưu trữ để giữ mọi đối tượng được tạo trong suốt thời gian chạy, một triển khai GC không có op là hoàn toàn hợp lệ. Vì vậy, trong khi bạn có thể quan sát những thay đổi hành vi trong bao nhiêu lần các dấu hiệu Timer, điều này là tốt vì không có đảm bảo về những gì bạn sẽ thấy dựa trên cách bạn đang gọi nó. (Đây là khái niệm tương tự như cách một bộ đếm thời gian chạy trong một nhiệm vụ chuyên sâu CPU sẽ đánh dấu nhiều lần hơn khi hệ thống được tải - không phải kết quả là sai vì giao diện không cung cấp loại bảo đảm đó.)

điểm này tôi giới thiệu bạn trở lại câu đầu tiên trong câu trả lời này. :)

+0

Cảm ơn bạn đã trả lời. Tôi sẽ chỉnh sửa câu hỏi của mình và thêm ví dụ về mã ở đó. – Ivan

+1

Tôi đã thêm ví dụ mã vào bài đăng của mình, nó hiển thị hành vi chương trình khác nhau trên các triển khai khác nhau của.Net và với mục tiêu xây dựng khác nhau, do tối ưu hóa đó gây ra. – Ivan

+0

Tuổi thọ thay đổi trong môi trường lớp lót! = Với ngôn ngữ trong ngôn ngữ, imo hành vi như vậy là không trực quan và về lý thuyết có thể phá vỡ mã, mặc dù tôi không nghĩ đó thực sự là một vấn đề. Chỉ cần tự hỏi nếu thông số kỹ thuật Java nói điều gì đó về các trường hợp như vậy. Sử dụng GC trông ngày càng phức tạp hơn mỗi ngày :) – Ivan

1

Java, nói chung, có hành vi rằng nếu đối tượng có thể truy cập trong phạm vi (có tham chiếu đến nó không phải là rác), thì đối tượng không phải là rác. Đây là đệ quy, vì vậy nếu a là tham chiếu đến đối tượng có tham chiếu đến b, đối tượng b là không phải là rác.

Trong phạm vi mà bạn vẫn có thể đạt đối tượng được tham chiếu bởi ref, (bạn có thể thêm một dòng System.out.println(ref.toString())), ref không phải là rác.

Tuy nhiên, theo this old source from Sun's site, phần lớn phụ thuộc vào việc triển khai cụ thể của JVM.

Các vấn đề liên quan