.NET의 MemoryStream이 닫히지 않으면 메모리 누수가 발생합니까?


112

다음 코드가 있습니다.

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

내가 할당 한 MemoryStream이 나중에 폐기되지 않을 가능성이 있습니까?

나는 이것을 수동으로 닫아야한다고 주장하는 피어 리뷰를 받았는데 그가 유효한 포인트가 있는지 여부를 알 수있는 정보를 찾을 수 없습니다.


41
리뷰어가 그것을 닫아야한다고 생각 하는지 정확히 물어보십시오 . 그가 일반적인 모범 사례에 대해 이야기한다면 그는 아마도 똑똑 할 것입니다. 그가 더 일찍 기억을 풀어주는 것에 대해 이야기한다면, 그는 틀 렸습니다.
Jon Skeet

답변:


60

일회용품이라면 항상 폐기해야합니다. Disposed를 확인하기 위해 메서드에 using문을 사용해야합니다 .bar()ms2

결국 가비지 수집기에 의해 정리되지만 항상 Dispose를 호출하는 것이 좋습니다. 코드에서 FxCop을 실행하면 경고로 플래그가 지정됩니다.


16
using 블록은 dispose를 호출합니다.
Nick

20
@Grauenwolf : 귀하의 주장이 캡슐화를 중단합니다. 소비자로서 당신은 그것이 작동하지 않는지 신경 쓰지 말아야합니다. IDisposable이라면 그것을 Dispose ()하는 것이 당신의 일입니다.
Marc Gravell

4
StreamWriter 클래스에는 해당되지 않습니다. StreamWriter 를 폐기하는 경우 에만 연결된 스트림 을 폐기합니다. 가비지 수집을 받고 종료자가 호출되면 스트림을 폐기하지 않습니다. 이것은 의도적으로 설계된 것입니다.
springy76

4
이 질문이 2008 년의 질문이라는 것을 알고 있지만 현재는 .NET 4.0 Task 라이브러리가 있습니다. 폐기는 ()입니다 불필요한 에서 대부분의 경우 작업을 사용하는 경우. IDisposable "완료되면 이걸 처리하는 게 좋을 것" 의미 해야 한다는 데 동의하지만 , 더 이상 그 의미는 아닙니다.
Phil

7
또 다른 예를 들어 당신해야하지 처분으로 IDisposable 개체 HttpClient를하다 aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong BCL에서 그냥 다른 예를 들어이는 IDisposable 목적은 당신이 필요하지 (또는 안) 않는 경우 처분은 그것. ) 이건 그냥 보통 일반적인 규칙 일부 예외도 BCL에 있다는 것을 기억하는 것입니다
MARIUSZ Pawelski

166

최소한 현재 구현에서는 아무것도 유출하지 않습니다.

Dispose를 호출하면 MemoryStream에서 사용하는 메모리가 더 빨리 정리되지 않습니다. 그것은 또는 당신에게 유용하지 않을 수도 있습니다 호출 후 읽기 / 쓰기 통화 가능한되는 것을 스트림을 중지합니다.

당신이 절대적으로 당신이하는 것이 있다면 결코 스트림의 다른 종류에 MemoryStream을에서 이동하지 않으려는, 당신에게 폐기를 호출하지 어떤 해를 할 수 없을거야. 그러나, 당신이 이제까지 경우 부분적으로 있기 때문에 일반적으로 좋은 연습이다 다른 스트림을 사용하는 변화를, 당신은 당신이 초기에 쉬운 방법을 선택하기 때문에 찾기 어려운 버그 물리지하지 않으려는. (반면에 YAGNI 논쟁이 있습니다 ...)

어쨌든이를 수행하는 또 다른 이유는 새로운 구현 Dispose에서 해제되는 리소스를 도입 할 수 있다는 것입니다.


이 경우 함수는 "호출 매개 변수에 따라 다르게 해석 될 수있는 데이터"를 제공하기 때문에 MemoryStream을 반환하고 있으므로 바이트 배열 일 수 있지만 다른 이유로 MemoryStream으로 수행하는 것이 더 쉬웠습니다. 따라서 확실히 다른 Stream 클래스가 아닙니다.
Coderer

이 경우에도 일반적인 원칙 (좋은 습관 등을 기르는 등)에 따라 처리 하려고 했지만 까다로워 져도별로 걱정하지 않습니다.
Jon Skeet

1
리소스를 최대한 빨리 해제하는 것이 정말 걱정된다면 "using"블록 직후에 참조를 제거하여 관리되지 않는 리소스 (있는 경우)가 정리되고 개체가 가비지 수집 대상이됩니다. 메서드가 즉시 반환되는 경우에는 큰 차이가 없을 것입니다. 그러나 메서드에서 더 많은 메모리를 요청하는 것과 같은 다른 작업을 계속하면 확실히 차이를 만들 수 있습니다.
Triynko

@Triynko 사실이 아님 : 자세한 내용은 stackoverflow.com/questions/574019/… 를 참조하십시오.
George Stocker

10
YAGNI 주장은 두 가지 방법으로 모두 취할 수 있습니다. 구현하는 것을 폐기하지 않기로 결정하는 것은 IDisposable정상적인 모범 사례에 위배되는 특별한 경우이므로 YAGNI 하에서 실제로 필요할 때까지하지 말아야하는 경우라고 주장 할 수 있습니다. 원리.
Jon Hanna

26

예 거기 누출 은 누출을 정의하고 나중에 얼마나 큰 의미 방식에 따라 ...

누수로 인해 "메모리가 할당 된 상태로 남아 있어도 사용할 수 없음"을 의미하고 후자의 경우 dispose를 호출 한 후 언제든지 사용할 수 있음을 의미하는 경우, 영구적 인 것은 아니지만 누수가있을 수 있습니다 (예 : 애플리케이션 런타임의 수명).

MemoryStream을에서 사용하는 관리되는 메모리를 확보하기 위해, 당신은 그것을 unreference 할 필요가 그래서 바로 가비지 컬렉션 대상이되고, 그것에 대한 참조를 무효로. 이 작업을 수행하지 않으면 사용을 마친 시점부터 참조가 범위를 벗어날 때까지 일시적인 누수가 발생합니다. 그 동안 메모리를 할당에 사용할 수 없기 때문입니다.

using 문 (단순히 dispose를 호출하는 것보다)의 이점은 using 문에서 참조를 선언 할 수 있다는 것입니다. using 문이 완료되면 dispose가 호출 될뿐만 아니라 참조가 범위를 벗어나서 참조를 효과적으로 무효화하고 "reference = null"코드를 작성하는 것을 기억할 필요없이 즉시 가비지 수집 대상 개체를 만듭니다.

무언가를 즉시 참조 해제하지 않는 것은 고전적인 "영구적 인"메모리 누수는 아니지만 확실히 동일한 효과를가집니다. 예를 들어, Dispose를 호출 한 후에도 MemoryStream에 대한 참조를 유지하고 메서드에서 조금 더 아래로 더 많은 메모리를 할당하려고하면 ... 여전히 참조 된 메모리 스트림에서 사용중인 메모리를 사용할 수 없습니다. 참조를 무효화하거나 dispose를 호출하여 사용을 완료하더라도 범위를 벗어날 때까지


6
나는이 응답을 좋아한다. 때때로 사람들은 사용의 두 가지 의무, 즉 열렬한 자원 회수 열렬한 역 참조를 잊어 버립니다 .
Kit

1
실제로 Java와 달리 C # 컴파일러는 "마지막 사용 가능"을 감지하므로 변수가 마지막 참조 이후 범위를 벗어나는 경우 마지막 사용 가능 직후 가비지 수집 대상이 될 수 있습니다. . 실제로 범위를 벗어나기 전에. 참조 stackoverflow.com/questions/680550/explicit-nulling
Triynko

2
가비지 수집기와 지터는 그런 식으로 작동하지 않습니다. 범위는 언어 구성이며 런타임이 준수하는 것이 아닙니다. 실제로 블록이 끝날 때 .Dispose ()에 대한 호출을 추가하여 참조가 메모리에있는 시간을 늘릴 수 있습니다. 참조 ericlippert.com/2015/05/18/…
Pablo Montilla

8

전화 .Dispose()(또는로 래핑 Using)는 필요하지 않습니다.

전화 한 이유 .Dispose()가능한 한 빨리 자원해제하기 위해서 입니다.

예를 들어 제한된 메모리 세트와 수천 개의 요청이 들어오는 Stack Overflow 서버를 생각해보십시오. 예정된 가비지 수집을 기다리지 않고 최대한 빨리 메모리를 해제하여 사용할 수 있도록합니다. 새로운 수신 요청.


24
MemoryStream에서 Dispose를 호출해도 메모리가 해제되지는 않습니다. 실제로 Dispose를 호출 한 후에도 MemoryStream의 데이터를 가져올 수 있습니다. 시도해보십시오.
Jon Skeet

12
-1 MemoryStream의 경우 사실이지만 일반적인 조언으로 이것은 명백한 잘못입니다. Dispose는 파일 핸들 또는 데이터베이스 연결과 같은 관리되지 않는 리소스 를 해제 하는 것입니다. 기억은 그 범주에 속하지 않습니다. 거의 항상 예약 된 가비지 수집이 메모리를 해제 할 때까지 기다려야합니다.
Joe

1
FileStream객체 를 할당하고 배치하는 데 하나의 코딩 스타일을 채택하고 객체에 대해 다른 코딩 스타일을 채택하면 어떤 이점이 MemoryStream있습니까?
Robert Rossney

3
FileStream에는 Dispose를 호출하면 실제로 즉시 해제 될 수있는 관리되지 않는 리소스가 포함됩니다 . 반면에 MemoryStream은 관리되는 바이트 배열을 해당 _buffer 변수에 저장하며 , 이는 폐기시 해제되지 않습니다. 실제로 _buffer는 MemoryStream의 Dispose 메서드에서도 null이되지 않습니다. 이는 참조를 null로 설정하면 메모리가 폐기 시점에 바로 GC에 적합하도록 만들 수 있기 때문에 SHAMEFUL BUG IMO입니다. 대신 느린 (그러나 삭제 된) MemoryStream 참조가 여전히 메모리를 유지합니다. 따라서 폐기 한 후에는 여전히 범위 내에있는 경우 null도해야합니다.
Triynko

@Triynko- "그러므로 일단 폐기하면 여전히 범위 내에 있으면 무효화해야합니다."-동의하지 않습니다. Dispose를 호출 한 후 다시 사용하면 NullReferenceException이 발생합니다. Dispose 후에 다시 사용하지 않으면 null을 사용할 필요가 없습니다. GC는 충분히 똑똑합니다.
Joe

8

이것은 이미 답변되어 있지만, 정보 은닉의 좋은 구식 원칙은 향후 언젠가 리팩토링을 원할 수 있음을 의미합니다.

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

에:

Stream foo()
{    
   ...
}

이것은 호출자가 어떤 종류의 Stream이 반환되는지 신경 쓰지 않아야 함을 강조하고 내부 구현을 변경할 수있게합니다 (예 : 단위 테스트를 위해 모의 할 때).

바 구현에서 Dispose를 사용하지 않은 경우 문제가 발생해야합니다.

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5

모든 스트림은 IDisposable을 구현합니다. 메모리 스트림을 using 문으로 감싸면 멋지고 멋질 것입니다. using 블록은 스트림이 무엇이든 상관없이 닫히고 삭제되도록합니다.

Foo를 호출 할 때마다 (MemoryStream ms = foo ()) 사용할 수 있으며 여전히 괜찮을 것이라고 생각합니다.


1
이 습관과 관련하여 내가 겪은 한 가지 문제는 스트림이 다른 곳에서 사용되고 있지 않은지 확인해야한다는 것입니다. 예를 들어 MemoryStream을 가리키는 JpegBitmapDecoder를 만들고 Frames [0]을 반환했지만 (데이터를 자체 내부 저장소에 복사 할 것이라고 생각했습니다) 비트 맵이 시간의 20 % 만 표시된다는 사실을 발견했습니다. 메모리 스트림을 처리하고있었습니다.
devios1 2010-08-13

메모리 스트림이 지속되어야하는 경우 (예 : using 블록이 의미가 없음) Dispose를 호출하고 즉시 변수를 null로 설정해야합니다. 폐기하면 더 이상 사용되지 않으므로 즉시 null로 설정해야합니다. chaiguy가 설명하는 것은 자원 관리 문제처럼 들립니다. 왜냐하면 당신이 그것을 건네주고있는 것이 그것을 처리하는 책임을지고 참조를 나눠주는 것이 더 이상 책임이 없다는 것을 알지 않는 한 당신은 무언가에 대한 참조를 나누어서는 안되기 때문입니다. 그렇게.
Triynko

2

메모리 누수는 발생하지 않지만 코드 검토자가 스트림을 닫아야한다고 표시하는 것이 정확합니다. 그렇게하는 것이 예의입니다.

메모리 누수가 발생할 수있는 유일한 상황은 실수로 스트림에 대한 참조를 남겨두고 닫지 않는 경우입니다. 당신은 여전히 정말 메모리가 누수되지 않습니다,하지만 당신은 하는 당신이 그것을 사용하고 주장하는 것을 불필요하게 시간을 연장.


1
> 여전히 실제로 메모리가 누출되는 것은 아니지만 사용한다고 주장하는 시간을 불필요하게 연장하고 있습니다. 확실합니까? Dispose는 메모리를 해제하지 않으며 함수에서 늦게 호출하면 실제로 수집 할 수없는 시간이 늘어날 수 있습니다.
Jonathan Allen

2
네, 조나단이 요점이 있습니다. 함수 늦게 Dispose를 호출하면 실제로 컴파일러가 함수의 매우 늦게 스트림 인스턴스에 액세스해야한다고 생각할 수 있습니다. 컴파일러가 함수의 초기에 최적의 릴리스 지점 (일명 "마지막 사용 지점")을 계산할 수 있기 때문에 dispose를 전혀 호출하지 않는 것보다 더 나쁠 수 있습니다 (따라서 스트림 변수에 대한 함수 후반 참조를 피함). .
Triynko

2

나는에 MemoryStream을 포장 추천 bar()A의 using중심으로 일관성을 위해 문 :

  • 현재 MemoryStream은에서 메모리를 해제하지 않지만 .Dispose()미래의 어느 시점에서 또는 귀하 (또는 귀하의 회사의 다른 사람)가이를 수행하는 사용자 지정 MemoryStream으로 대체 할 수 있습니다.
  • 모든 스트림이 폐기 되도록 프로젝트에서 패턴을 설정하는 것이 도움이됩니다. "일부 스트림은 폐기해야하지만 특정 스트림은 폐기 할 필요가 없습니다."대신 "모든 스트림을 폐기해야합니다"라고 말하여 선을 더 확실하게 그립니다. ...
  • 다른 유형의 Streams를 반환 할 수 있도록 코드를 변경 한 경우 어쨌든 삭제하도록 변경해야합니다.

foo()IDisposable을 생성하고 반환 할 때와 같은 경우 에 일반적으로 수행하는 또 다른 작업 은 객체 생성 사이의 실패 return가 예외에 의해 포착되고 객체를 처리하고 예외가 다시 발생하는지 확인하는 것입니다 .

MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    // "other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

1

개체가 IDisposable을 구현하는 경우 완료되면 .Dispose 메서드를 호출해야합니다.

일부 개체에서 Dispose는 닫기와 동일하며 그 반대의 경우도 마찬가지입니다.

이제 특정 질문에 대해 아니오, 메모리 누수가 발생하지 않습니다.


3
"Must"는 매우 강력한 단어입니다. 규칙이있을 때마다 규칙 위반의 결과를 아는 것이 좋습니다. MemoryStream의 경우 결과가 거의 없습니다.
Jon Skeet

-1

나는 .net 전문가는 아니지만 아마도 여기서 문제는 리소스, 즉 메모리가 아닌 파일 핸들입니다. 나는 가비지 수집기가 결국 스트림을 해제하고 핸들을 닫을 것이라고 생각하지만, 내용을 디스크에 플러시하도록 명시 적으로 닫는 것이 항상 가장 좋은 방법이라고 생각합니다.


MemoryStream은 모두 메모리 내에 있습니다. 여기에는 파일 핸들이 없습니다.
Jon Skeet

-2

관리되지 않는 리소스의 폐기는 가비지 수집 언어에서 결정적이지 않습니다. Dispose를 명시 적으로 호출하더라도 백업 메모리가 실제로 해제되는시기를 전혀 제어 할 수 없습니다. Dispose는 using 문을 종료하거나 하위 메서드에서 호출 스택을 표시하여 개체가 범위를 벗어날 때 암시 적으로 호출됩니다. 이 모든 것이 말하면, 때때로 객체는 실제로 관리 자원 (예 : 파일)의 래퍼 일 수 있습니다. 그래서 finally 문에서 명시 적으로 닫거나 using 문을 사용하는 것이 좋습니다. 건배


1
정확히 사실이 아닙니다. Dispose는 using 문을 종료 할 때 호출됩니다. 개체가 범위를 벗어나면 Dispose가 호출되지 않습니다.
Alexander Abramov

-3

MemorySteram은 관리되는 객체 인 바이트 배열 일뿐입니다. 폐기하거나 닫는 것을 잊으십시오. 이것은 최종 결정의 오버 헤드 외에는 부작용이 없습니다.
리플렉터에서 MemoryStream의 constuctor 또는 flush 메소드를 확인하면 좋은 관행을 따르는 것 외에 닫거나 폐기하는 것에 대해 걱정할 필요가없는 이유가 분명해질 것입니다.


6
-1 : 4 년 이상 된 질문에 답변이 허용 된 글을 게시하려는 경우 유용한 것으로 만드십시오.
Tieson T.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.