2009-04-24 31 views
5

Mã mẫu đơn giản này chứng tỏ sự cố. Tôi tạo một ArrayBlockingQueue và một luồng chờ dữ liệu trên hàng đợi này bằng cách sử dụng take(). Sau khi vòng lặp kết thúc, trên lý thuyết cả hàng đợi và luồng có thể được thu thập rác, nhưng trong thực tế, tôi sớm nhận được một số OutOfMemoryError. Điều gì ngăn cản điều này là GC'd, và làm thế nào điều này có thể được sửa?OutOfMemoryError - tại sao Chủ đề chờ đợi không được thu gom rác?

/** 
* Produces out of memory exception because the thread cannot be garbage 
* collected. 
*/ 
@Test 
public void checkLeak() { 
    int count = 0; 
    while (true) { 

     // just a simple demo, not useful code. 
     final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
     final Thread t = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        abq.take(); 
       } catch (final InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
     t.start(); 

     // perform a GC once in a while 
     if (++count % 1000 == 0) { 
      System.out.println("gc"); 
      // this should remove all the previously created queues and threads 
      // but it does not 
      System.gc(); 
     } 
    } 
} 

Tôi đang sử dụng Java 1.6.0.

CẬP NHẬT: thực hiện GC sau một vài lần lặp, nhưng điều này không giúp ích gì.

Trả lời

8

Chủ đề là các đối tượng cấp cao nhất. Chúng là 'đặc biệt' để chúng không tuân theo các quy tắc giống như các đối tượng khác. Không dựa vào tài liệu tham khảo để giữ cho chúng 'sống' (tức là an toàn từ GC). Một sợi sẽ không bị thu gom rác cho đến khi nó kết thúc. Điều này không xảy ra trong mã mẫu của bạn, vì chuỗi này bị chặn. Tất nhiên, bây giờ mà đối tượng thread không phải là rác thu thập được, sau đó bất kỳ đối tượng khác được tham chiếu bởi nó (hàng đợi trong trường hợp của bạn) cũng không thể được thu gom rác.

0

Bạn bắt đầu chuỗi, vì vậy tất cả các chuỗi mới sẽ chạy không đồng bộ trong khi vòng lặp tiếp tục tạo chuỗi mới.

Vì mã của bạn đang khóa, các chuỗi là các tham chiếu về cuộc sống trong hệ thống và không thể được thu thập. Nhưng ngay cả khi họ đã làm một số công việc, các chủ đề không có khả năng được chấm dứt nhanh như chúng được tạo ra (ít nhất là trong mẫu này), và do đó GC không thể thu thập tất cả bộ nhớ và cuối cùng sẽ thất bại với một OutOfMemoryException.

Tạo nhiều chuỗi không hiệu quả cũng như không hiệu quả. Nếu nó không phải là một yêu cầu để có tất cả những hoạt động đang chờ xử lý chạy song song, bạn có thể muốn sử dụng một hồ bơi thread và một hàng đợi của runnables để xử lý.

+0

Tôi không nghĩ đó là vấn đề, ngay cả khi tôi chạy System.gc() trong mỗi vòng lặp, các chuỗi không bị hủy. – martinus

+1

@martinus đọc kỹ hơn. Lượng thời gian cần để thu gom rác dài hơn thời gian cần thiết để tạo chủ đề. Nếu bạn bắt đầu làm đầy bồn tắm không bị cản trở và bạn thêm nước nhanh hơn ống dẫn nước, bồn tắm cuối cùng sẽ lấp đầy và tràn. Đó là điều kiện phân bổ tài nguyên/phân bổ deallocation. Điều gì sẽ xảy ra nếu bạn chỉ tạo 100 chuỗi và tiếp tục trong vòng lặp while? Các chủ đề có được thu thập cuối cùng không? – Wedge

+0

@ wedge, không, họ không bao giờ được thu thập! hoặc làm điều gì đó khác. Ngay cả khi tôi đợi một giây ở cuối mỗi vòng lặp thì không có gì là GC. – martinus

5

Bạn đang tạo chủ đề vô thời hạn vì tất cả chúng đều bị chặn cho đến khi ArrayBlockingQueue<Integer> abq có một số mục nhập. Vì vậy, cuối cùng bạn sẽ nhận được OutOfMemoryError.

(chỉnh sửa)

Mỗi chủ đề mà bạn tạo ra sẽ không bao giờ chấm dứt vì nó ngăn chặn cho đến khi hàng đợi abq như một entry. Nếu chuỗi đang chạy, GC sẽ không thu thập bất kỳ đối tượng nào mà chuỗi đang tham chiếu bao gồm hàng đợi abq và chính chuỗi đó.

+0

có nhưng hàng đợi không được tham chiếu nữa khi vòng lặp vượt quá – martinus

+1

Tại sao bạn nói vòng lặp kết thúc? vòng lặp tiếp tục mãi mãi ... –

+0

Tôi có nghĩa là khi kết thúc vòng lặp bắt đầu lại trên đầu, hàng đợi và chuỗi đã tạo trước đó không được tham chiếu nữa. – martinus

0

Vòng lặp while của bạn là vòng lặp vô hạn và tạo chuỗi liên tục mới. Mặc dù bạn bắt đầu thực hiện luồng ngay sau khi nó được tạo nhưng thời gian thực hiện nhiệm vụ của nó bằng luồng lớn hơn thời gian lấy nó để tạo luồng.

Ngoài ra những gì đang làm với tham số abq bằng cách khai báo nó bên trong vòng lặp while?

Dựa trên các chỉnh sửa của bạn và các nhận xét khác. System.gc() không đảm bảo chu trình GC. Đọc tuyên bố của tôi ở trên tốc độ thực hiện của chủ đề của bạn là thấp hơn tốc độ sáng tạo.

Tôi đã kiểm tra nhận xét cho phương thức take() "Truy xuất và loại bỏ phần đầu của hàng đợi này, chờ đợi nếu không có phần tử nào trên hàng đợi này". Tôi thấy bạn định nghĩa ArrayBlockingQueue nhưng bạn không thêm bất kỳ phần tử nào vào nó vì vậy tất cả luồng của bạn chỉ đang chờ đợi phương thức đó, đó là lý do tại sao bạn nhận được OOM.

2
abq.put(0); 

nên lưu ngày của bạn.

Chủ đề của bạn tất cả chờ đợi trên hàng đợi take() nhưng bạn không bao giờ đặt bất kỳ thứ gì vào hàng đợi đó.

+0

Đây rõ ràng là câu trả lời đúng! –

0

Tôi không biết các luồng được triển khai bằng Java như thế nào, nhưng có thể lý do tại sao hàng đợi và chủ đề không được thu thập: Chủ đề có thể là trình bao bọc cho các luồng hệ thống sử dụng nguyên gốc đồng bộ hóa hệ thống. tự động thu thập một chủ đề chờ đợi, vì nó không thể biết liệu luồng có còn sống hay không, tức là GC chỉ đơn giản là không biết rằng một luồng không thể được đánh thức.

Tôi không thể nói cách tốt nhất để khắc phục nó là gì, vì tôi cần biết bạn đang cố gắng làm gì, nhưng bạn có thể xem java.util.concurrent để xem nó có lớp để làm gì không bạn cần.

0

Gọi System.gc không có gì vì không có gì để thu thập. Khi một chủ đề bắt đầu nó tăng số lượng tham chiếu chủ đề, không làm như vậy sẽ có nghĩa là các chủ đề sẽ chấm dứt không xác định. Khi phương thức chạy của luồng hoàn thành, khi đó số lượng tham chiếu của luồng được giảm đi.

while (true) { 
    // just a simple demo, not useful code. 
    // 0 0 - the first number is thread reference count, the second is abq ref count 
    final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
    // 0 1 
    final Thread t = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       abq.take(); 
       // 2 2 
      } catch (final InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    }); 
    // 1 1 
    t.start(); 
    // 2 2 (because the run calls abq.take) 
    // after end of loop 
    // 1 1 - each created object's reference count is decreased 
} 

Bây giờ, có một điều kiện chủng tộc tiềm năng - những gì nếu vòng lặp chính chấm dứt và không thu gom rác thải trước khi thread t có một cơ hội để làm bất cứ xử lý, tức là nó bị đình chỉ bởi hệ điều hành trước khi abq.take tuyên bố được thực hiện? Phương thức chạy sẽ cố gắng truy cập đối tượng abq sau khi GC đã phát hành nó, điều này sẽ là xấu.

Để tránh tình trạng cuộc đua, bạn nên chuyển đối tượng làm tham số cho phương thức chạy. Tôi không chắc chắn về Java những ngày này, nó được một thời gian, vì vậy tôi muốn đề nghị đi qua các đối tượng như là một tham số constructor cho một lớp học có nguồn gốc từ Runnable. Bằng cách đó, có một tham chiếu bổ sung cho abq được thực hiện trước khi phương thức chạy được gọi, do đó đảm bảo đối tượng luôn hợp lệ.

+0

Java không sử dụng tính tham chiếu cho GC. – TrayMan

+0

Tìm kiếm nhanh xác nhận không có tính tham chiếu. Thay thế 'số tham chiếu' bằng 'số tham chiếu đến đối tượng'. Bộ nhớ được sử dụng bởi các đối tượng chỉ được phát hành khi không có tham chiếu đến đối tượng. Trong mã mẫu, có các tham chiếu được thực hiện mà OP không nhận ra ở đó. – Skizz

+0

Ngay cả khi đếm tham chiếu, điều kiện chủng tộc có thể dễ dàng ngăn chặn bằng cách cho phép lệnh 't.start()' tăng số tham chiếu 't'. Bây giờ khi 't' nằm ngoài phạm vi, vẫn có một tham chiếu được tính, vì vậy không có GC nào xảy ra trên' t'. Như đã lưu ý, Java không sử dụng tính tham chiếu, nhưng ý tưởng cơ bản là giống nhau: Một luồng được đánh dấu đang chạy (và vì vậy được ngăn chặn từ GC) ngay khi phương thức 'start()' được gọi, không chỉ khi luồng mới bắt đầu thực hiện. –

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