2009-04-24 4 views
5

이 간단한 샘플 코드는 문제를 보여줍니다. 나는 ArrayBlockingQueue을 만들고, take()을 사용하여이 큐의 데이터를 기다리는 스레드를 만듭니다. 루프가 끝나면 이론적으로 대기열과 스레드 모두 가비지 수집 될 수 있지만 실제로는 OutOfMemoryError이됩니다. 이것이 GC가되지 못하게하는 이유는 무엇이며이를 어떻게 수정할 수 있습니까?OutOfMemoryError - 대기중인 스레드가 가비지 수집되지 않는 이유는 무엇입니까?

/** 
* 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(); 
     } 
    } 
} 

Java 1.6.0을 사용하고 있습니다.

업데이트 : 몇 번의 반복 작업 후에 GC를 수행하지만 이는 도움이되지 않습니다.

답변

8

스레드는 최상위 수준 개체입니다. 그들은 '특별'하므로 다른 객체와 동일한 규칙을 따르지 않습니다. 그들은 '살아있다'(GC로부터 안전함) 유지를위한 참조에 의존하지 않습니다. 스레드는 종료 될 때까지 가비지 수집을하지 않습니다. 스레드가 차단되어 있으므로 샘플 코드에서 발생하지 않습니다. 물론 스레드 객체가 가비지 수집되지 않았으므로이 객체가 참조하는 다른 객체 (사례의 대기열)도 가비지 수집 될 수 없습니다.

0

새 스레드가 비동기 적으로 실행되는 동안 루프가 새 스레드를 계속 작성하므로 스레드를 시작하십시오.

코드가 잠겨 있기 때문에 스레드는 시스템의 수명 참조이므로 수집 할 수 없습니다. 그러나 일부 작업을 수행하는 경우에도 스레드는 생성 된 것처럼 빨리 종료되지 않으므로 GC는 모든 메모리를 수집 할 수 없으므로 결국 OutOfMemoryException으로 실패합니다.

많은 수의 스레드를 생성하는 것이 효율적이지 않으며 효율적이지 않습니다. 보류중인 모든 작업을 병렬로 실행해야하는 요구 사항이 아닌 경우 스레드 풀과 실행 큐를 사용하여 처리 할 수 ​​있습니다.

+0

각 루프에서 System.gc()를 실행해도 스레드가 파괴되지 않는다고해서 문제가되지 않는다고 생각합니다. – martinus

+1

@martinus 더 자세히 읽으십시오. 스레드를 가비지 수집하는 데 걸리는 시간은 스레드를 만드는 데 걸리는 시간보다 깁니다. 멈추지 않은 욕조를 채우고 욕조 배수구보다 빨리 물을 넣으면 욕조가 결국 채워져 넘치게됩니다. 자원 할당/할당 해제 경쟁 조건입니다. 100 개의 스레드 만 만들고 while 루프를 계속하면 어떻게됩니까? 스레드가 결국 수집됩니까? – Wedge

+0

@ 웨지, 아니, 그들은 수집되지 않는다! 또는 다른 것을하십시오. 각 루프가 끝날 때까지 1 초를 기다릴 때도 아무 것도 GC'd되지 않습니다. – martinus

5

ArrayBlockingQueue<Integer> abq에 항목이있을 때까지 모두 차단되기 때문에 무기한으로 스레드를 만듭니다. 따라서 결국 OutOfMemoryError이 표시됩니다.

(편집)

당신이 하나 개의 항목으로 abq 큐까지 그 때문에 블록을 종료하지 않습니다 작성하는 각 스레드. 스레드가 실행중인 경우 GC는 스레드 abq과 스레드 자체를 포함하여 스레드가 참조하는 개체를 수집하지 않습니다.

+0

예. 루프가 끝났을 때 큐가 더 이상 참조되지 않습니다. – martinus

+1

루프가 끝났다고하는 이유는 무엇입니까? 루프는 영원히 계속됩니다 ... –

+0

루프의 끝이 맨 위에서 다시 시작될 때 이전에 생성 된 큐와 스레드는 더 이상 참조되지 않습니다. – martinus

0

while 루프는 무한 루프이며 지속적으로 새로운 스레드를 생성합니다. 스레드 생성을 시작하자마자 실행을 시작했지만 스레드가 작업을 완료하는 데 소요되는 시간은 스레드 작성 시간보다 길다.

while 루프 내에서 abq 매개 변수를 선언하면 어떻게됩니까?

편집 및 기타 의견을 기반으로합니다. System.gc()는 GC주기를 보장하지 않습니다. 스레드의 실행 속도 이상의 내 문장을 읽는 것이 생성 속도보다 빠릅니다.

take() 메소드에 대한 주석을 검사했습니다. "이 대기열의 헤드를 가져 와서 제거합니다. 대기열에 요소가없는 경우 대기 중입니다." ArrayBlockingQueue를 정의하지만 요소를 추가하지 않으면 모든 스레드가 해당 메서드를 기다리고 있으므로 OOM을 얻는 것입니다.

2
abq.put(0); 

귀하의 하루를 저장해야합니다.

스레드는 모두 대기열의 take()에서 대기하지만 그 대기열에는 아무 것도 넣지 않습니다.

+0

이것은 분명히 정답입니다! –

0

Java에서 스레드가 구현되는 방법을 모르지만 대기열과 스레드가 수집되지 않는 이유를 생각해 볼 수 있습니다. 시스템 동기 프리미티브를 사용하는 시스템 스레드에 대한 래퍼가 스레드가 될 수 있습니다. 스레드가 살아 있는지 여부를 알 수 없으므로 대기중인 스레드를 자동으로 수집합니다. 즉, GC는 스레드가 깨어날 수 없음을 단순히 알 수 없습니다.

당신이 무엇을하려고하는지 알 필요가 있기 때문에 가장 좋은 방법은 무엇인지 말할 수는 없지만, java.util.concurrent에서 무엇을 할 수있는 클래스가 있는지 살펴볼 수 있습니다. 너는 필요해.

0

System.gc은 수집 할 것이 없으므로 아무 것도하지 않습니다. 스레드가 시작될 때 스레드 참조 카운트를 증가시키지 않으면 스레드가 불확정하게 종료 함을 의미합니다. thread의 run 메소드가 완료하면 (자), thread의 참조 카운트가 감소합니다. 메인 루프가 종료되고 스레드 t은 어떤 처리를 할 수있는 기회를하기 전에 가비지 수집을 수행하는 경우, 즉 그것이 abq.take 전에 OS에 의해 일시 중단 된 것 -

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 
} 

지금, 잠재적 인 경쟁 조건이 문이 실행됩니까? run 메소드는 GC가 그것을 해제 한 후에 abq 객체에 접근하려고 시도하는데, 이것은 좋지 않을 것이다.

경쟁 조건을 피하려면 개체를 매개 변수로 run 메서드에 전달해야합니다. 요즘 Java에 대해서는 잘 모르겠다. 잠시 지났으므로 객체를 생성자 매개 변수로 전달하여 Runnable에서 파생 된 클래스에 전달하는 것이 좋습니다. 이렇게하면 run 메소드가 호출되기 전에 abq에 대한 추가 참조가 생성되므로 객체가 항상 유효하다는 것을 확인할 수 있습니다.

+0

Java는 GC에 대한 참조 카운팅을 사용하지 않습니다. – TrayMan

+0

빠른 검색은 참조 계산이 없음을 확인합니다. '참조 횟수'를 '개체 참조 횟수'로 바꿉니다. 객체가 사용하는 메모리는 객체에 대한 참조가 없을 때만 해제됩니다. 샘플 코드에는 OP가 실현되지 않았다는 참조가 있습니다. – Skizz

+0

참조 카운팅을하더라도,'t.start()'호출이't'의 참조 카운트를 증가시킴으로써 경쟁 조건을 쉽게 막을 수 있습니다. 이제't'가 범위를 벗어나면 여전히 하나의 참조가 계산되므로't'에서 GC가 발생하지 않습니다. Java가 참조 카운팅을 사용하지는 않지만 기본 아이디어는 같습니다. 스레드는 새 스레드가 시작될 때뿐만 아니라 start() 메소드가 호출되는 즉시 실행 중으로 표시되므로 GC에서 차단됩니다. 실행을 시작합니다. –