2013-05-01 34 views
9

Đây là một câu hỏi rất cơ bản. Tôi sẽ xây dựng nó bằng cách sử dụng C++ và Java, nhưng nó thực sự là ngôn ngữ độc lập. Xem xét vấn đề nổi tiếng trong C++:Bộ sưu tập rác và quản lý bộ nhớ thủ công

struct Obj 
{ 
    boost::shared_ptr<Obj> m_field; 
}; 

{ 
    boost::shared_ptr<Obj> obj1(new Obj); 
    boost::shared_ptr<Obj> obj2(new Obj); 
    obj1->m_field = obj2; 
    obj2->m_field = obj1; 
} 

Đây là rò rỉ bộ nhớ và mọi người đều biết điều đó :). Giải pháp cũng nổi tiếng: người ta nên sử dụng con trỏ yếu để phá vỡ "khóa liên động". Nó cũng được biết rằng vấn đề này không thể được giải quyết tự động về nguyên tắc. Đó là trách nhiệm của lập trình viên duy nhất để giải quyết nó.

Nhưng có một điều tích cực: một lập trình viên có toàn quyền kiểm soát các giá trị số lần truy cập. Tôi có thể tạm dừng chương trình của tôi trong trình gỡ lỗi và kiểm tra refcount cho obj1, obj2 và hiểu rằng có một vấn đề. Tôi cũng có thể thiết lập một điểm ngắt trong destructor của một đối tượng và quan sát một thời điểm hủy diệt (hoặc tìm ra rằng đối tượng đã không bị phá hủy).

Câu hỏi của tôi là về Java, C#, ActionScript và các ngôn ngữ "Bộ sưu tập rác" khác. Tôi có thể thiếu cái gì, nhưng theo ý kiến ​​của tôi họ

  1. Đừng để tôi kiểm tra refcount của các đối tượng
  2. Đừng cho tôi biết khi đối tượng bị phá hủy (okay, khi đối tượng được tiếp xúc với GC)

Tôi thường nghe rằng những ngôn ngữ này chỉ không cho phép một lập trình viên làm rò rỉ bộ nhớ và đó là lý do tại sao chúng tuyệt vời. Theo như tôi hiểu, họ chỉ ẩn các vấn đề quản lý bộ nhớ và làm cho nó khó giải quyết chúng.

Cuối cùng, câu hỏi bản thân:

Java:

public class Obj 
{ 
    public Obj m_field; 
} 

{ 
    Obj obj1 = new Obj(); 
    Obj obj2 = new Obj(); 
    obj1.m_field = obj2; 
    obj2.m_field = obj1; 
} 
  1. Có bộ nhớ bị rò rỉ?
  2. Nếu có: làm cách nào để phát hiện và khắc phục sự cố?
  3. Nếu không: tại sao?
+1

Nó không phải là rò rỉ bộ nhớ. Nó không ** bảo vệ bạn khỏi bị rò rỉ bộ nhớ, nhưng không có gì để ngăn cản bạn giải phóng các đối tượng đó trong destructor. Quản lý bộ nhớ là một phần của ** ứng dụng ** thiết kế; hacks cấp thấp sẽ không bù đắp cho việc thiếu thiết kế. –

+0

không cho phép một lập trình viên để rò rỉ bộ nhớ không phải là trường hợp, nhưng những langauges có thể bảo vệ bạn khỏi rò rỉ bộ nhớ trong hầu hết các trường hợp.Đây là một lợi thế lớn cho những lập trình viên không có bất kỳ ý tưởng về bộ nhớ, chúng tôi ít nhất Không cần phải lo lắng quá nhiều về việc rò rỉ bộ nhớ khi gán cho chúng một số dự án nhỏ – StereoMatching

+0

Không có quyền truy cập vào tài khoản vì hầu hết các triển khai không duy trì số lần truy cập, và các ngôn ngữ nói chung cẩn thận không áp đặt các chi tiết triển khai. điều này ngăn việc triển khai tốt hơn - nhanh hơn, mạnh mẽ hơn, hữu ích hơn, v.v.). – delnan

Trả lời

8

Hệ thống bộ nhớ được quản lý được xây dựng dựa trên giả định rằng bạn không muốn truy tìm vấn đề rò rỉ bộ nhớ ngay từ đầu. Thay vì làm cho họ dễ dàng hơn để giải quyết bạn cố gắng để đảm bảo rằng họ không bao giờ xảy ra ở nơi đầu tiên.

Java không có cụm từ bị mất cho "Memory Leak" có nghĩa là bất kỳ sự tăng trưởng nào trong bộ nhớ có thể ảnh hưởng đến ứng dụng của bạn, nhưng không bao giờ có một điểm mà bộ nhớ được quản lý không thể dọn sạch tất cả bộ nhớ.

JVM không sử dụng tính tham khảo đối với một số lý do

  • nó không thể xử lý tài liệu tham khảo tròn như bạn đã quan sát thấy.
  • nó có bộ nhớ đáng kể và luồng trên không để duy trì chính xác.
  • có nhiều cách tốt hơn, đơn giản hơn để xử lý các tình huống như vậy đối với bộ nhớ được quản lý.

Mặc dù JLS không cấm sử dụng số lượng tham chiếu, nhưng nó không được sử dụng trong bất kỳ AFMIK JVM nào. Thay vào đó, Java theo dõi một số ngữ cảnh gốc (ví dụ: mỗi ngăn xếp luồng) và có thể theo dõi các đối tượng nào cần được lưu giữ và có thể loại bỏ các đối tượng này dựa trên việc các đối tượng đó có thể truy cập mạnh hay không. Nó cũng cung cấp các thiết bị cho các tài liệu tham khảo yếu (được giữ lại miễn là các đối tượng không được làm sạch) và các tài liệu tham khảo mềm (thường không được làm sạch nhưng có thể theo ý của người thu gom rác)

5

AFAIK, Java GC hoạt động bằng cách bắt đầu từ một tập hợp các tham chiếu ban đầu được xác định rõ và tính toán một đối tượng chuyển tiếp liên tục. Bất cứ điều gì không thể truy cập là "bị rò rỉ" và có thể là GC-ed.

+0

Có thể chúng được xác định rõ cho Java, nhưng không phải cho tôi :). Tôi có thể tìm hiểu thêm về các tài liệu tham khảo này ở đâu? – Nick

+0

@Nick Tôi không làm việc trong Java, vì vậy tôi tiếc là không biết. Hãy thử tài liệu Java hoặc chú Google. – Angew

+0

Tài liệu tham khảo cơ bản cho thu gom rác nói chung là [_The Garbage Collection Handbook_] (http://gchandbook.org/) của Richard Jones, Antony Hosking và Eliot Moss. Nó có các cuộc thảo luận rộng rãi nhất, nếu không phải tất cả, về các chiến lược quản lý bộ nhớ khác nhau (bao gồm cả việc đếm tham chiếu được shared_ptr sử dụng). –

1

Sự khác biệt quan trọng là trong Java, v.v. bạn không tham gia vào vấn đề xử lý ở tất cả. Điều này có thể cảm thấy như một vị trí khá đáng sợ nhưng nó là đáng ngạc nhiên trao quyền. Tất cả các quyết định bạn đã sử dụng để thực hiện đối với người chịu trách nhiệm xử lý một đối tượng đã tạo đã biến mất.

Điều đó thực sự có ý nghĩa. Hệ thống biết nhiều hơn về những gì có thể tiếp cận được và những gì không phải là bạn. Nó cũng có thể đưa ra quyết định linh hoạt hơn và thông minh hơn về thời điểm xé nát các cấu trúc, vv ..

Về bản chất - trong môi trường này bạn có thể sắp xếp các đối tượng theo cách phức tạp hơn mà không phải lo lắng về việc thả một. Điều duy nhất bạn cần phải lo lắng là nếu bạn vô tình dán một cái lên trần nhà.

Là một lập trình viên C cũ đã chuyển sang Java Tôi cảm thấy nỗi đau của bạn.

Re - câu hỏi cuối cùng của bạn - nó không phải là một rò rỉ bộ nhớ.Khi GC đá vào mọi thứ bị loại trừ ngoại trừ những gì có thể truy cập được. Trong trường hợp này, giả sử bạn đã phát hành obj1obj2 cả hai đều không thể truy cập để cả hai đều bị loại bỏ.

+0

"Hệ thống biết nhiều hơn về những gì có thể truy cập và những gì không phải là bạn". Hmmm ... Nghe có vẻ tốt cho đến khi tôi không có vấn đề về trí nhớ. Nhưng tôi nên làm gì khi chương trình của tôi hết bộ nhớ? Tôi biết làm thế nào để điều tra và tối ưu hóa bộ nhớ manegement trong "C++ như" môi trường. Nhưng tôi nên làm gì trong Java? – Nick

+0

@Nick Trong Java, bạn nên a) tăng dung lượng bộ nhớ hoặc b) tối ưu hóa chương trình của bạn để sử dụng ít bộ nhớ hơn. Bạn có thể sử dụng một bộ nhớ hồ sơ trong cả hai trường hợp để xác định giải pháp tốt nhất là gì. Java đi kèm với một VisualVM miễn phí mà bạn có thể đính kèm vào bất kỳ quá trình chạy nào và trong khi nó không phải là tuyệt vời, đó là nơi tốt để bắt đầu. Tối ưu hóa việc sử dụng bộ nhớ là Java đơn giản vì nhiều vấn đề trong C++ không xảy ra hoặc b) khó hơn nhiều vì bạn không có nhiều cách để nén từng byte cuối cùng của hệ thống. Do bạn mua 32 GB với giá 300 đô la, tôi đi với tùy chọn a) –

1

Bộ sưu tập rác không phải là ref đơn giản đếm.

Ví dụ tham khảo vòng tròn mà bạn trình bày sẽ không xảy ra trong một ngôn ngữ được quản lý thu gom rác vì bộ thu gom rác sẽ muốn theo dõi các tham chiếu phân bổ tất cả cách quay lại thứ gì đó trên ngăn xếp. Nếu không có tham chiếu ngăn xếp ở đâu đó, đó là rác. Các hệ thống đếm số liệu như shared_ptr không phải là thông minh và có thể (như bạn chứng minh) có hai đối tượng ở đâu đó trong vùng heap để giữ cho nhau không bị xóa.

0

Các ngôn ngữ được thu gom rác không cho phép bạn kiểm tra trình thu thập dữ liệu vì chúng không có ai. Bộ sưu tập rác là một điều hoàn toàn khác với việc quản lý bộ nhớ được truy cập lại. Sự khác biệt thực sự là quyết định.

{ 
std::fstream file("example.txt"); 
// do something with file 
} 
// ... later on 
{ 
std::fstream file("example.txt"); 
// do something else with file 
} 

trong C++ bạn có đảm bảo rằng example.txt đã bị đóng sau khi chặn đầu tiên hoặc nếu ngoại lệ bị ném. Caomparing nó với Java

{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 
// ..later on 
{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 

Như bạn thấy, bạn đã giao dịch quản lý bộ nhớ cho tất cả quản lý tài nguyên khác. Đó là sự khác biệt thực sự, các đối tượng được giải phóng vẫn giữ sự hủy diệt xác định. Trong các ngôn ngữ thu gom rác, bạn phải tự giải phóng tài nguyên và kiểm tra ngoại lệ. Người ta có thể lập luận rằng quản lý bộ nhớ rõ ràng có thể tẻ nhạt và dễ bị lỗi, nhưng trong C++ hiện đại, bạn bị giảm thiểu bởi các con trỏ thông minh và các thùng chứa tiêu chuẩn. Bạn vẫn có một số trách nhiệm (tham khảo vòng tròn, ví dụ), nhưng hãy suy nghĩ xem có bao nhiêu catch/finally block bạn có thể tránh sử dụng hủy diệt xác định và bao nhiêu gõ một Java/C#/etc.lập trình viên phải làm thay vào đó (vì chúng phải đóng thủ công/giải phóng tài nguyên khác với bộ nhớ). Và tôi biết rằng có sử dụng cú pháp trong C# (và một cái gì đó tương tự trong Java mới nhất) nhưng nó chỉ bao gồm tuổi thọ phạm vi khối và không phải là vấn đề chung chung về quyền sở hữu được chia sẻ.

2

Java có chiến lược quản lý bộ nhớ duy nhất. Tất cả mọi thứ (ngoại trừ một vài điều cụ thể) được phân bổ trên heap, và không được giải phóng cho đến khi GC được làm việc.

Ví dụ:

public class Obj { 
    public Object example; 
    public Obj m_field; 
} 

public static void main(String[] args) { 
    int lastPrime = 2; 
    while (true) { 
     Obj obj1 = new Obj(); 
     Obj obj2 = new Obj(); 
     obj1.example = new Object(); 
     obj1.m_field = obj2; 
     obj2.m_field = obj1; 
     int prime = lastPrime++; 
     while (!isPrime(prime)) { 
      prime++; 
     } 
     lastPrime = prime; 
     System.out.println("Found a prime: " + prime); 
    } 
} 

C xử lý tình trạng này bằng cách yêu cầu bạn phải tự giải phóng bộ nhớ của cả hai 'obj', và C++ đếm tham chiếu đến 'obj' và tự động tiêu diệt chúng khi họ đi ra khỏi phạm vi . Java không không phải là miễn phí bộ nhớ này, ít nhất là không phải lúc đầu.

Thời gian chạy Java chờ một thời gian cho đến khi nó cảm thấy như có nhiều bộ nhớ đang được sử dụng. Sau đó, bộ thu gom rác đổ vào.

Cho phép nói trình thu gom rác java quyết định dọn sạch sau lần lặp thứ 10.000 của vòng lặp ngoài. Bởi thời gian này 10.000 đối tượng đã được tạo ra (mà có thể đã được giải phóng trong C/C++).

Mặc dù có 10.000 lần lặp của vòng lặp ngoài, chỉ obj1 và obj2 mới được tạo có thể được tham chiếu bởi mã.

Đây là các 'rễ' của GC, mà java sử dụng để tìm tất cả các đối tượng có thể được tham chiếu. Bộ thu gom rác sau đó đệ quy lặp lại cây đối tượng, đánh dấu 'ví dụ' là hoạt động trong nghiện cho rễ thu gom rác.

Tất cả các đối tượng khác sau đó sẽ bị bộ thu gom rác phá hủy. Điều này đi kèm với một hình phạt hiệu suất, nhưng quá trình này đã được tối ưu hóa rất nhiều, và không có ý nghĩa đối với hầu hết các ứng dụng.

Không giống như trong C++, bạn không phải lo lắng về chu kỳ tham chiếu ở tất cả, vì chỉ đối tượng có thể truy cập từ gốc GC mới được phát hành.

Với các ứng dụng java bạn do phải lo lắng về bộ nhớ (Hãy suy nghĩ danh sách giữ các đối tượng từ tất cả các lần lặp lại), nhưng nó không đáng kể như các ngôn ngữ khác.

Để gỡ lỗi: ý tưởng gỡ lỗi giá trị bộ nhớ cao của Java đang sử dụng 'bộ phân tích bộ nhớ' đặc biệt để tìm ra đối tượng nào vẫn còn trên heap, không phải lo lắng về những gì đang tham chiếu cái gì.

+4

Chiến lược quản lý bộ nhớ của Java hầu như không phải là duy nhất. Bộ sưu tập rác ngày trở lại Lisp. Ngoài ra, các JVM khác nhau có thể thực hiện thu gom rác khác nhau. –

+0

@JayElston Lisp đã phát minh ra bộ sưu tập rác? Và tôi biết sự đơn giản của nó, các JVM có rất nhiều thời gian để thu thập dữ liệu như thế nào :) – Techcable

+0

Nếu bạn tin [Wikipedia] (https://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29) :-) –