"메모리 누수"의 해부학


172

.NET 관점에서 :

  • 메모리 누수 란 무엇입니까 ?
  • 애플리케이션 누출 여부를 어떻게 확인할 수 있습니까? 효과는 무엇입니까?
  • 메모리 누수를 어떻게 방지 할 수 있습니까?
  • 응용 프로그램에 메모리 누수가있는 경우 프로세스가 종료되거나 종료 될 때 사라 집니까? 또는 프로세스 완료 후에도 응용 프로그램의 메모리 누수가 시스템의 다른 프로세스에 영향을 줍니까?
  • COM Interop 및 / 또는 P / Invoke를 통해 액세스하는 관리되지 않는 코드는 어떻습니까?

답변:


110

내가 본 가장 좋은 설명은 무료 프로그래밍 기초 전자 책 7 장에 있습니다. 있습니다.

기본적으로 .NET에서 에서 참조 된 개체가 루팅되면 메모리 누수가 발생하여 가비지 수집 할 수 없습니다. 의도 한 범위를 벗어난 참조를 붙잡을 때 실수로 발생합니다.

OutOfMemoryExceptions가 시작되거나 메모리 사용량이 예상했던 것 ( PerfMon)을 넘어 설 때 누수가 있음을 알게 될 것입니다 에는 멋진 메모리 카운터가 있습니다).

.NET 의 메모리 모델을 이해 하는 것이 피하는 가장 좋은 방법입니다. 특히 가비지 수집기의 작동 방식과 참조의 작동 방식을 이해합니다. 전자 책 7 장을 참조하십시오. 또한 일반적인 함정, 특히 가장 일반적인 사건을 염두에 두십시오. 객체의 경우 A는 객체에 이벤트에 등록되어 B , 다음 객체 A는 객체 때까지 곁에 것 B가 있기 때문에 사라 B가 에 대한 참조를 보유하고 . 해결책은 완료되면 이벤트를 등록 취소하는 것입니다.

물론 좋은 메모리 프로파일을 사용하면 객체 그래프를보고 객체의 중첩 / 참조를 탐색하여 참조가 어디서 오는지, 어떤 루트 객체가 책임이 있는지 확인할 수 있습니다 ( 레드 게이트 개미 프로파일 , JetBrains dotMemory, memprofiler 가 정말 좋습니다) 텍스트 전용 WinDbgSOS를 사용할 수 있습니다. 있지만 실제 전문가가 아니라면 상업용 / 비주얼 제품을 강력히 권장합니다).

관리되지 않는 코드는 공유 참조가 가비지 수집기에 의해 관리되는 것을 제외하고 일반적인 메모리 누수의 영향을받습니다. 이 마지막 요점에 대해 틀릴 수 있습니다.


11
오, 당신은 책을 좋아합니까? 저자가 때때로 stackoverflow에서 팝업되는 것을 보았습니다.
Johnno Nolan

일부 .NET 객체는 또한 스스로 뿌리를 내리고 수집 할 수 없게됩니다. IDisposable 인 것은이 때문에 폐기해야합니다.
kyoryu

1
@kyoryu : 객체 자체는 어떻게 뿌리를 내립니까?
Andrei Rînea

2
@Andrei : 실행중인 스레드가 객체 루팅 자체의 가장 좋은 예라고 생각합니다. 정적 비공개 위치 (예 : 정적 이벤트 구독 또는 정적 필드 초기화로 싱글 톤 구현)에서 자신에 대한 참조를 넣는 객체는 명백한 방법이 없기 때문에 그 자체로 루팅되었을 수 있습니다. ... 그것의 계류에서 "뿌리".
제프리 한틴

@Jeffry 이것은 무슨 일이 일어나고 있는지 설명하는 확실한 방법입니다.
Exitos

35

엄밀히 말하면, 메모리 누수로 인해 프로그램에서 더 이상 사용하지 않는 메모리가 소비되고 있습니다.

"더 이상 사용되지 않음"은 하나 이상의 의미를 갖습니다. "더 이상 참조하지 않음", 즉 완전히 복구 불가능 함을 의미하거나 참조, 복구 가능, 사용되지 않음을 의미하지만 프로그램은 참조를 유지합니다. 완벽하게 관리되는 개체에 대해서는 나중에 .Net에만 적용됩니다 . 그러나 모든 클래스가 완벽하지는 않으며 기본적으로 관리되지 않는 기본 구현에서 해당 프로세스에 대한 리소스가 영구적으로 누출 될 수 있습니다.

모든 경우에 응용 프로그램은 반드시 필요한 것보다 많은 메모리를 사용합니다. 유출 된 양에 따라 부작용이 발생하지 않고 과도한 수집으로 인한 속도 저하, 일련의 메모리 예외 및 마지막으로 치명적인 오류가 발생하여 프로세스가 강제 종료 될 수 있습니다.

모니터링시 각 가비지 수집주기 후에 프로세스에 더 많은 메모리가 할당 된 것으로 표시 될 때 응용 프로그램에 메모리 문제가 있음을 알고 있습니다. 입니다. 이 경우 메모리를 너무 많이 유지하거나 일부 관리되지 않는 구현이 누출되고 있습니다.

대부분의 누수의 경우 프로세스가 종료 될 때 리소스가 복구되지만 일부 정확한 리소스에서 일부 리소스가 항상 복구되는 것은 아니며 GDI 커서 핸들이 악명 높습니다. 물론 프로세스 간 통신 메커니즘이있는 경우 다른 프로세스에 할당 된 메모리는 해당 프로세스가 해제하거나 종료 될 때까지 해제되지 않습니다.


32

"메모리 누수 란 무엇인가"와 "효과는 무엇인가?"라는 질문에 대해 이미 잘 대답했다고 생각하지만 다른 질문에 몇 가지 더 추가하고 싶었습니다 ...

응용 프로그램 유출 여부를 이해하는 방법

흥미로운 방법 중 하나는 perfmon 을 열고 모든 힙# Gen 2 모음 에서 # 바이트에 대한 트레이스를 추가 하는 것입니다. 특정 기능을 사용하면 총 바이트 수가 증가하고 해당 메모리가 다음 Gen 2 콜렉션 후에 할당 된 상태로 유지되면 기능이 메모리를 누출한다고 말할 수 있습니다.

방지하는 방법

다른 좋은 의견이 주어졌습니다. .NET 메모리 누수 의 가장 일반적으로 간과 된 원인은 이벤트 처리기를 제거하지 않고 객체에 추가하는 것입니다. 객체에 연결된 이벤트 핸들러는 해당 객체에 대한 참조 형식이므로 다른 모든 참조가 끝난 후에도 수집을 방지합니다. 항상 이벤트 핸들러를 분리해야합니다 (-= C # 구문을 합니다.

프로세스가 종료되면 누출이 사라지고 COM interop은 어떻습니까?

프로세스가 종료되면 DLL에서 제공되는 모든 COM 개체를 포함하여 주소 공간에 매핑 된 모든 메모리가 OS에 의해 회수됩니다. 비교적 드물게 COM 개체를 별도의 프로세스에서 제공 할 수 있습니다. 이 경우 프로세스가 종료 될 때 사용한 COM 서버 프로세스에 할당 된 메모리에 대한 책임은 여전히 ​​사용자에게 있습니다.


19

메모리 누수는 객체가 완료된 후 할당 된 모든 메모리를 해제하지 않는 객체로 정의합니다. Windows API 및 COM (버그가 있거나 관리되지 않는 관리되지 않는 코드), 프레임 워크 및 타사 구성 요소를 사용하는 경우 응용 프로그램에서 발생할 수 있습니다. 펜과 같은 특정 물체를 사용한 후에도 문제가 발생할 수 있음을 발견했습니다.

개인적으로 발생할 수 있지만 도트 네트 응용 프로그램의 메모리 누수에만 국한되지 않는 메모리 부족 예외가 발생했습니다. (OOM은 고정 에서 올 수도 있습니다. Pinning Artical 참조 . ). OOM 오류가 발생하지 않거나 메모리 누수가 원인인지 확인해야하는 경우 응용 프로그램을 프로파일 링하는 것이 유일한 방법입니다.

또한 다음을 시도하고 확인합니다.

a) Idisposable을 구현하는 모든 것은 finally 블록을 사용하거나 브러쉬, 펜 등을 포함한 using 문을 사용하여 폐기됩니다 (일부 사람들은 모든 것을 아무것도 설정하지 않았다고 주장합니다)

b) close 메서드가있는 모든 것은 finally 또는 using 문을 사용하여 다시 닫힙니다 (사용한 것으로 찾은 것은 using 문 외부에서 객체를 선언했는지 여부에 따라 항상 닫히지는 않습니다)

c) 관리되지 않는 코드 / Windows API를 사용하는 경우 이후에 올바르게 처리됩니다. (일부에는 리소스를 해제하는 정리 방법이 있습니다)

도움이 되었기를 바랍니다.


19

.NET에서 메모리 누수를 진단해야하는 경우 다음 링크를 확인하십시오.

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

이 기사에서는 프로세스의 메모리 덤프를 작성하는 방법과이를 분석하여 누출이 관리되지 않는지 관리되는지 여부와 관리되는지 여부를 파악하는 방법에 대해 설명합니다.

또한 Microsoft는 DebugDiag라는 ADPlus를 대체하기 위해 충돌 덤프 생성을 지원하는 새로운 도구를 제공합니다.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en



15

가비지 수집기의 작동 방식에 대한 가장 좋은 설명은 Jeff Richters CLR (C # book, 20 장)을 참조하십시오. 이것을 읽으면 객체가 어떻게 지속되는지 이해하기위한 훌륭한 근거가됩니다.

실수로 객체를 루팅하는 가장 일반적인 원인 중 하나는 이벤트를 연결하여 클래스를 벗어나는 것입니다. 외부 이벤트를 연결하면

예 :

SomeExternalClass.Changed += new EventHandler(HandleIt);

처분 할 때 연결을 끊는 것을 잊어 버리면 SomeExternalClass는 클래스를 참조합니다.

위에서 언급했듯이 SciTech 메모리 프로파일 러 는 누출이 의심되는 객체의 근본을 보여줍니다.

그러나 특정 유형을 확인하는 매우 빠른 방법은 WnDBG를 사용하는 것입니다 (연결되어있는 동안 VS.NET 즉시 창에서도 사용할 수 있음).

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

이제 해당 유형의 객체를 폐기 할 것으로 생각되는 작업을 수행하십시오 (예 : 창 닫기). 여기에 System.GC.Collect()몇 번 실행 되는 디버그 버튼이 있으면 편리합니다 .

그런 다음 !dumpheap -stat -type <TypeName>다시 실행 하십시오. 숫자가 내려 가지 않았거나 예상대로 내려 가지 않았다면 추가 조사의 근거가됩니다. (이 팁은 Ingo Rammer가 제공 한 세미나에서 얻었습니다 ).


14

관리되는 환경에서는 큰 메모리 덩어리에 대한 불필요한 참조를 유지하는 것이 누출 일 것입니다.


11

사람들은 왜 .NET의 메모리 누수가 다른 누수와 같지 않다고 생각합니까?

메모리 누수는 리소스에 연결하여 놓지 않는 경우입니다. 관리되는 코드와 관리되지 않는 코딩 모두에서이 작업을 수행 할 수 있습니다.

.NET 및 기타 프로그래밍 도구와 관련하여 가비지 수집 및 응용 프로그램 누수를 유발하는 상황을 최소화하는 다른 방법에 대한 아이디어가 있습니다. 그러나 메모리 누수를 방지하는 가장 좋은 방법은 사용중인 플랫폼에서 기본 메모리 모델과 작동 방식을 이해해야한다는 것입니다.

GC와 다른 마술이 당신의 혼란을 정리한다고 믿는 것은 메모리 누수의 짧은 길이며 나중에 찾기가 어려울 것입니다.

관리되지 않는 코드를 작성할 때는 일반적으로 정리를 수행해야합니다. 보유한 리소스가 관리인이 아니라 정리해야 할 책임이 있음을 알고 있습니다.

반면에 .NET에서는 많은 사람들이 GC가 모든 것을 정리할 것이라고 생각합니다. 글쎄, 그것은 당신을 위해 몇 가지를하지만, 당신은 그렇게해야합니다. .NET은 많은 작업을 처리하므로 관리되는 리소스 나 관리되지 않는 리소스를 다루고 있는지 항상 알지 못하며 처리중인 내용을 확인해야합니다. 글꼴, GDI 리소스, Active Directory, 데이터베이스 등을 처리하는 것이 일반적으로주의해야합니다.

관리되는 용어로 프로세스가 종료 / 제거되면 목이 줄을 지어 줄 것입니다.

나는 많은 사람들이 이것을 가지고 있음을 알며, 이것이 끝날 것을 정말로 희망합니다. 엉망을 정리하기 위해 사용자에게 앱을 종료하도록 요청할 수 없습니다! IE, FF 등이 될 수있는 브라우저를 살펴본 다음 Google 리더를 열고 며칠 동안 그대로두고 어떤 일이 발생하는지 살펴보세요.

그런 다음 브라우저에서 다른 탭을 열고 일부 사이트로 이동 한 다음 브라우저 누수를 일으킨 다른 페이지를 호스팅 한 탭을 닫으면 브라우저가 메모리를 해제한다고 생각하십니까? IE에서는 그렇지 않습니다. 내 컴퓨터에서 IE는 Google 리더를 사용하면 짧은 시간 (약 3-4 일)에 1GiB의 메모리를 쉽게 먹습니다. 일부 뉴스 페이지는 더 나쁩니다.


10

관리되는 환경에서는 큰 메모리 덩어리에 대한 불필요한 참조를 유지하는 것이 누출 일 것입니다.

물론. 또한 적절한 경우 일회용 개체에 .Dispose () 메서드를 사용하지 않으면 멤 누수가 발생할 수 있습니다. 가장 쉬운 방법은 자동으로 끝에 .Dispose ()를 실행하기 때문에 using 블록을 사용하는 것입니다.

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

관리되지 않는 개체를 사용하는 클래스를 만드는 경우 IDisposable을 올바르게 구현하지 않으면 클래스 사용자에게 메모리 누수가 발생할 수 있습니다.


9

모든 메모리 누출은 프로그램 종료에 의해 해결됩니다.

메모리가 부족하면 운영 체제에서 사용자를 대신하여 문제를 해결하기로 결정할 수 있습니다.


8

나는 .net에서 멤 누출이 무엇인지 Bernard와 동의 할 것입니다.

메모리 사용을 확인하기 위해 응용 프로그램을 프로파일 링하고, 메모리를 많이 관리하지 않는 경우 누출이 있다고 말할 수 있습니다.

관리되는 용어로 프로세스가 종료 / 제거되면 목이 줄을 지어 줄 것입니다.

관리되지 않는 코드는 자체의 짐승이며 그 안에 누출이 있으면 표준 mem을 따릅니다. 누출 정의.


7

또한 .NET에는 두 개의 힙이 있으며 하나는 큰 객체 힙입니다. 나는이 힙에 대략 85k 이상의 물체가 놓여 있다고 믿는다. 이 힙에는 일반 힙과 다른 수명 규칙이 있습니다.

큰 메모리 구조 (사전 또는 목록)를 작성하는 경우 정확한 규칙이 무엇인지 찾아 보는 것이 좋습니다.

프로세스 종료시 메모리를 회수하는 한, Win98 또는 이와 동등한 장치를 실행하지 않으면 종료시 모든 것이 OS로 다시 릴리스됩니다. 크로스 프로세스와 다른 프로세스에서 여전히 리소스가 열려있는 것은 예외입니다.

COM 개체는 까다로울 수 있습니다. 항상 IDispose패턴을 사용하면 안전합니다. 그러나 나는 구현하는 몇 가지 interop 어셈블리를 실행했습니다 IDispose. 여기서 핵심 Marshal.ReleaseCOMObject은 작업이 끝나면 전화 하는 것입니다. COM 개체는 여전히 표준 COM 참조 계산을 사용합니다.


6

.Net Memory Profiler 가 .Net에서 메모리 누수를 찾을 때 매우 도움이 된다는 것을 알았습니다 . 그것은 Microsoft CLR Profiler처럼 무료가 아니지만 내 의견으로는 더 빠르고 더 빠릅니다. ㅏ


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.