메모리 리크
Memory leak프로그램 실행 |
---|
일반적인 개념 |
코드의 종류 |
컴파일 전략 |
주목할 만한 런타임 |
주목할 만한 컴파일러 및 툴 체인 |
컴퓨터 과학에서 메모리 누수는 컴퓨터 프로그램이 더 이상 필요하지 않은 메모리를 해제하지 않는 방식으로 메모리 할당을[1] 잘못 관리할 때 발생하는 리소스 누수입니다.메모리 누수는 오브젝트가 메모리에 저장되어 있지만 실행 중인 [2]코드로 액세스할 수 없는 경우에도 발생할 수 있습니다.메모리 누수는 다른 많은 문제와 유사한 증상을 나타내며 일반적으로 프로그램의 소스 코드에 액세스할 수 있는 프로그래머만이 진단할 수 있습니다.
관련 개념은 "공간 누출"입니다. 이는 프로그램이 과도한 메모리를 소비하지만 결국 메모리를 [3]방출하는 것입니다.
애플리케이션 실행 시 사용 가능한 시스템메모리를 소진할 수 있기 때문에 메모리 누수는 소프트웨어 에이징의 원인 또는 원인이 되는 경우가 많습니다.
결과들
메모리 누수는 사용 가능한 메모리의 양을 줄임으로써 컴퓨터의 성능을 저하시킵니다.최악의 경우, 사용 가능한 메모리가 너무 많이 할당되어 시스템 또는 디바이스의 전부 또는 일부가 올바르게 동작하지 않게 되거나 응용 프로그램이 실패하거나 스레싱으로 인해 시스템이 크게 느려질 수 있습니다.
메모리 누수는 심각하지 않을 수도 있고 일반적인 방법으로도 검출되지 않을 수도 있습니다.최신 운영 체제에서는 응용 프로그램이 종료될 때 응용 프로그램에서 사용되는 일반 메모리가 해제됩니다.즉, 짧은 시간 동안만 실행되는 프로그램에서 메모리 누수가 감지되지 않을 수 있으며 거의 심각하지 않습니다.
훨씬 더 심각한 누출에는 다음이 포함됩니다.
- 프로그램이 장시간 실행되어 서버의 백그라운드 태스크 등 시간이 지남에 따라 추가 메모리를 소비하며, 특히 수년 동안 실행 상태를 유지할 수 있는 임베디드 시스템에서 사용됩니다.
- 컴퓨터 게임 또는 애니메이션 비디오의 프레임을 렌더링할 때처럼 일회성 태스크에 새로운 메모리가 자주 할당되는 곳
- 프로그램이 종료된 경우에도 해제되지 않은 공유 메모리와 같은 메모리를 요구할 수 있는 경우
- 임베디드 시스템이나 휴대용 디바이스 등 메모리가 매우 한정되어 있는 경우, 또는 프로그램이 최초로 대량의 메모리를 필요로 하는 경우, 리크 여력이 거의 없는 경우.
- operating system 또는 메모리 매니저에서 리크가 발생했을 경우
- 시스템 디바이스 드라이버로 인해 누수가 발생했을 때
- 프로그램 종료 시 메모리가 자동으로 해제되지 않는 운영 체제에서 실행됩니다.
메모리 누수의 예
다음 예시는 의사 코드로 기술되어 있으며, 프로그래밍 지식이 없어도 메모리누설이 어떻게 발생할 수 있는지와 그 영향을 보여주는 것을 목적으로 하고 있습니다.이 경우 프로그램은 엘리베이터를 제어하기 위해 설계된 매우 간단한 소프트웨어의 일부입니다.이 프로그램은 엘리베이터 안에 있는 사람이 층 버튼을 누를 때마다 실행됩니다.
버튼을 누르면:플로어 번호를 기억하기 위해 사용할 메모리를 준비합니다. 플로어 번호를 메모리에 입력하세요. 목표 층에 이미 도착했습니까?이 경우 할 일이 없습니다.그렇지 않으면 종료됩니다.리프트가 유휴 상태가 될 때까지 기다립니다. 필요한 층으로 이동합니다. 층 번호를 기억하는 데 사용한 메모리를 해제합니다.
요청된 층 번호가 엘리베이터와 같은 층일 경우 메모리 누수가 발생합니다.메모리 해제 조건은 생략됩니다.이 문제가 발생할 때마다 더 많은 메모리가 누출됩니다.
이런 사건들은 보통 즉각적인 효과를 거두지 못할 것이다.사람들은 그들이 이미 있는 바닥의 버튼을 누르지 않는 경우가 많고, 어떤 경우든 엘리베이터에는 이런 일이 수백 번 또는 수천 번 일어날 수 있는 충분한 예비 메모리가 있을 수 있다.하지만 엘리베이터는 결국 메모리가 부족해질 것이다.이 작업은 몇 개월 또는 몇 년이 걸릴 수 있으므로 철저한 테스트에도 불구하고 발견되지 않을 수 있습니다.
그 결과는 불쾌할 것이다; 적어도 엘리베이터는 다른 층으로 이동하라는 요청에 대한 응답을 멈출 것이다(예를 들어 엘리베이터를 호출하려고 하거나 누군가가 안에 있고 바닥 버튼을 누르는 경우).프로그램의 다른 부분(예를 들어 문을 열고 닫도록 할당된 부분)이 필요한 경우, 아무도 들어갈 수 없으며, 누군가 안에 있을 경우 갇히게 됩니다(문을 수동으로 열 수 없는 경우).
시스템이 리셋 될 때까지 메모리 누수는 계속된다.예를 들어, 엘리베이터의 전원이 꺼지거나 정전이 되면 프로그램 실행이 중지됩니다.전원을 다시 켜면 프로그램이 재부팅되고 모든 메모리가 다시 사용 가능하지만, 메모리 누수가 느린 프로세스가 프로그램과 함께 다시 시작되어 시스템이 올바르게 동작하지 않게 됩니다.
위 예의 누출은 '해제' 작동을 조건부 범위를 벗어나게 함으로써 해결할 수 있습니다.
버튼을 누르면:플로어 번호를 기억하기 위해 사용할 메모리를 준비합니다. 플로어 번호를 메모리에 입력하세요. 목표 층에 이미 도착했습니까?그렇지 않은 경우: 리프트가 유휴 상태가 될 때까지 기다립니다. 필요한 층으로 이동합니다. 층 번호를 기억하는 데 사용한 메모리를 해제합니다.
프로그래밍 문제
메모리 누수는 프로그래밍에서 흔히 볼 수 있는 오류입니다.특히 C나 C++와 같이 자동 가비지 컬렉션이 내장되어 있지 않은 언어를 사용하는 경우에는 더욱 그렇습니다.일반적으로 메모리 누수는 동적으로 할당된 메모리가 도달할 수 없게 되었기 때문에 발생합니다.메모리 리크 버그가 만연함에 따라 도달 불가능한 메모리를 검출하기 위한 디버깅툴이 많이 개발되고 있습니다.BoundsChecker, Deleaker, IBM Rational Purify, Valgrind, Parasoft Insure++, Dr. 메모리와 메모리 워치는 C와 C++ 프로그램의 메모리 디버거 중 하나입니다."보수적인" 가비지 컬렉션 기능은 내장 기능으로 없는 프로그래밍 언어에 추가할 수 있으며 이를 위한 라이브러리는 C 및 C++ 프로그램에서 사용할 수 있습니다.보수적인 수집기는 도달 불가능한 메모리를 대부분 찾아 회수합니다.
메모리 매니저는 도달할 수 없는 메모리를 회복할 수 있지만 도달 가능한 메모리를 해방할 수 없기 때문에 아직 도움이 될 가능성이 있습니다.따라서 현대의 메모리 매니저는 프로그래머가 의미론적으로 메모리를 다양한 수준의 유용성으로 마크하는 기술을 제공하며, 이는 다양한 수준의 도달 가능성에 대응합니다.메모리 매니저는 강하게 도달할 수 있는 오브젝트를 해방시키지 않습니다.오브젝트는 강력한 참조를 통해 직접 또는 강력한 참조 체인을 통해 간접적으로 도달할 수 있는 경우 강하게 도달할 수 있습니다.(강력한 참조는 약한 참조와 달리 개체가 가비지 수집되는 것을 방지하는 참조입니다.)이를 방지하기 위해 개발자는 참조를 사용한 후 정리할 책임이 있습니다.일반적으로 참조가 필요 없게 되면 참조를 null로 설정하고 필요에 따라 오브젝트에 대한 강력한 참조를 유지하는 이벤트청취자의 등록을 해제합니다.
일반적으로 자동 메모리 관리는 개발자에게 더 강력하고 편리합니다. 왜냐하면 개발자는 해방 루틴을 구현하거나 청소가 수행되는 시퀀스에 대해 걱정할 필요가 없으며 객체가 여전히 참조되는지 여부에 대해 걱정할 필요가 없기 때문입니다.프로그래머는 객체가 더 이상 참조되지 않는 시기를 아는 것보다 참조가 더 이상 필요하지 않은 시기를 아는 것이 더 쉽습니다.그러나 자동 메모리 관리는 성능 오버헤드를 초래할 수 있으며 메모리 누수의 원인이 되는 프로그래밍 오류를 모두 제거하는 것은 아닙니다.
레이
RAII는 Resource Acquisition Is Initialization의 줄임말로 C++, D 및 Ada에서 일반적으로 발생하는 문제에 대한 접근법입니다.여기에는 범위가 지정된 개체를 가져온 리소스와 연결하고, 개체가 범위를 벗어나면 리소스를 자동으로 해제하는 작업이 포함됩니다.가비지 컬렉션과 달리 RAII는 객체가 언제 존재하는지 언제 존재하지 않는지 알 수 있는 장점이 있습니다.다음 C와 C++의 예를 비교합니다.
/* C 버전 */ #실패하다 <stdlib.h> 무효 f(인트 n) { 인트* 배열 = 하드 디스크(n, 크기(인트)); 일을 하다(배열); 공짜(배열); }
// C++ 버전 #실패하다 <blocks> 무효 f(인트 n) { 표준::벡터< >인트> 배열 (n); 일을 하다(배열); }
이 예에서 구현된 C 버전에서는 명시적인 할당 해제가 필요합니다.배열은 동적으로 할당되며(대부분의 C 구현에서는 힙에서), 명시적으로 해방될 때까지 계속 존재합니다.
C++ 버전에서는 명시적인 할당 해제가 필요하지 않습니다.오브젝트 즉시 항상 자동으로 실행됩니다.array
예외가 발생하는 경우를 포함하여 범위를 벗어납니다.이렇게 하면 가비지 수집 스키마의 오버헤드가 일부 방지됩니다.오브젝트 디스트럭터는 메모리 이외의 자원을 해방할 수 있기 때문에 RAII는 핸들을 통해 액세스되는 입력 및 출력 자원의 누설을 방지하는 데 도움이 됩니다.이러한 가비지 컬렉션은 정상적으로 처리되지 않습니다.여기에는 열려 있는 파일, 열려 있는 창, 사용자 알림, 그래픽 도면 라이브러리의 개체, 중요한 섹션, 네트워크 연결, Windows 레지스트리 또는 다른 데이터베이스 연결 등의 스레드 동기화 프리미티브가 포함됩니다.
그러나 RAII를 올바르게 사용하는 것이 항상 쉬운 것은 아니며 그 자체의 함정이 있습니다.예를 들어 주의를 기울이지 않으면 데이터를 참조로 반환하여 덩글링 포인터(또는 참조)를 만들 수 있지만 포함된 개체가 범위를 벗어나면 해당 데이터가 삭제됩니다.
D는 RAII와 가비지 수집의 조합을 사용하여 객체가 원래 범위를 벗어나 액세스할 수 없는 것이 분명한 경우 자동 파괴를 사용하고, 그렇지 않은 경우 가비지 수집을 사용합니다.
기준 계수 및 주기 기준
보다 최신의 가비지 수집 스킴은, 도달 가능성(reachability)의 개념에 근거하고 있는 경우가 많습니다.해당 메모리에 대한 사용 가능한 참조가 없는 경우는, 수집이 가능합니다.다른 가비지 수집 체계는 참조 카운트를 기반으로 할 수 있습니다. 여기서 개체는 참조 수를 추적합니다.숫자가 0으로 줄어들면 개체는 자체적으로 해제되고 해당 메모리를 회수할 수 있어야 합니다.이 모델의 단점은 주기적인 참조에 대처하지 못한다는 것입니다. 그래서 요즘 대부분의 프로그래머들은 더 비싼 마크와 스위프 유형의 시스템에 대한 부담을 받아들일 준비가 되어 있습니다.
다음 Visual Basic 코드는 표준 기준 카운트 메모리 누수를 나타냅니다.
어둡다 A, B 세트 A = Create Object(작성 객체)("뭔가") 세트 B = Create Object(작성 객체)("뭔가") 이 시점에서 두 개체는 각각 하나의 참조를 가집니다. 세트 A.멤버 = B 세트 B.멤버 = A 이제 그들은 각각 두 명의 추천서를 가지고 있다. 세트 A = 아무 것도 없어요. '그래도 빠져나갈 수 있을 텐데...' 세트 B = 아무 것도 없어요. '그리고 이제 기억이 새고 있구나!' 끝.
실제로, 이 사소한 예는 즉시 발견되어 수정될 것입니다.대부분의 실제 예에서는 참조 사이클이 3개 이상의 오브젝트에 걸쳐 있어 검출이 어렵습니다.
이러한 종류의 유출의 잘 알려진 예는 청취자 문제에서 웹 브라우저의 AJAX 프로그래밍 기법의 증가와 함께 두드러졌습니다.이벤트 핸들러에 DOM 요소를 관련지어 종료하기 전에 참조를 삭제하지 못한 JavaScript 코드는 메모리를 리크합니다(AJAX 웹 페이지는 기존 웹 페이지보다 훨씬 오랫동안 특정 DOM을 리크 상태로 유지하므로 이 리크가 훨씬 뚜렷합니다).
영향들
프로그램에서 메모리 누수가 발생하여 메모리 사용량이 꾸준히 증가하고 있는 경우 일반적으로 즉각적인 증상은 나타나지 않습니다.모든 물리 시스템에는 한정된 양의 메모리가 있습니다.메모리 리크가 억제되지 않으면(예를 들어 리크 프로그램을 재기동하는 등), 최종적으로 문제가 발생합니다.
대부분의 최신 소비자용 데스크톱 운영 체제에는 RAM 마이크로칩에 물리적으로 내장되어 있는 메인 메모리와 하드 드라이브 등의 세컨더리 스토리지가 모두 있습니다.메모리 할당은 동적으로 이루어집니다.각 프로세스에 필요한 만큼의 메모리가 할당됩니다.액티브 페이지는 메인 메모리로 전송되어 고속 액세스가 가능하게 됩니다.비활성 페이지는 필요에 따라 secondary 스토리지로 푸시되어 빈 공간이 확보됩니다.하나의 프로세스가 대량의 메모리를 소비하기 시작하면 보통 메인 메모리의 양이 증가하고 다른 프로그램이 세컨더리 스토리지로 밀려나 시스템의 퍼포먼스가 현저하게 저하됩니다.리크 프로그램이 종료되어도 다른 프로그램이 메인 메모리로 전환되어 퍼포먼스가 정상으로 돌아오기까지 다소 시간이 걸릴 수 있습니다.
시스템상의 모든 메모리가 소진되면(가상 메모리가 있는지 메인 메모리만 있는지, 임베디드 시스템상에 있는지 등), 메모리를 증설하려고 하면 실패합니다.이것에 의해, 통상, 메모리 할당을 시도하는 프로그램이 그 자체를 종료하거나 분할 장해를 발생시키는 원인이 됩니다.일부 프로그램은 이 상황에서 복구되도록 설계되어 있습니다(예약된 메모리로 폴백하는 경우도 있습니다).메모리 부족이 발생하는 첫 번째 프로그램은 메모리 누수가 있는 프로그램일 수도 있고 없는 프로그램일 수도 있습니다.
일부 멀티태스킹 운영체제에는 메모리 부족 상태에 대처하기 위한 특별한 메커니즘이 있습니다.예를 들어 프로세스를 랜덤하게 정지하거나('무결한' 프로세스에 영향을 줄 수 있음), 메모리 내의 가장 큰 프로세스를 정지하거나(아마도 이것이 문제의 원인일 것입니다).일부 운영 체제에는 프로세스별 메모리 제한이 있어 하나의 프로그램이 시스템상의 모든 메모리를 점유하는 것을 방지합니다.이 배치의 단점은 그래픽스, 비디오 또는 과학적 계산을 처리하는 프로그램 등 많은 양의 메모리를 필요로 하는 프로그램을 적절히 동작시킬 수 있도록 운영체제를 재구성해야 하는 경우가 있다는 것입니다.
커널에 메모리 누수가 있는 경우 운영체제 자체에 장애가 발생할 수 있습니다.임베디드 시스템 등 고도의 메모리 관리 기능이 없는 컴퓨터도 지속적인 메모리 누수로 인해 완전히 장애가 발생할 수 있습니다.
웹 서버나 라우터 등 공개적으로 액세스 가능한 시스템은 공격자가 누출을 트리거할 수 있는 일련의 조작을 발견하면 서비스 거부 공격을 받기 쉽습니다.이러한 시퀀스를 부정 이용이라고 합니다.
메모리 사용률의 「톱니」패턴은, 애플리케이션의 재기동이나 재기동시에 수직의 드롭이 발생하는 경우, 애플리케이션내의 메모리 누수의 지표가 되는 경우가 있습니다.그러나 가비지 수집 지점도 이러한 패턴을 일으킬 수 있고 힙의 정상적인 사용을 나타낼 수 있으므로 주의해야 합니다.
기타 메모리 컨슈머
메모리 사용량이 계속 증가하고 있다고 해서 반드시 메모리 누수의 증거가 되는 것은 아닙니다.애플리케이션에 따라서는, 메모리(캐시등)에 계속 증가하는 정보를 보존할 수 있습니다.캐시가 너무 커서 문제가 발생할 수 있는 경우 프로그래밍 오류 또는 설계 오류일 수 있지만 정보가 명목상 그대로 사용되므로 메모리 누수는 아닙니다.다른 경우에는 프로그래머가 메모리가 특정 태스크에 항상 충분하다고 가정했기 때문에 프로그램이 터무니없이 많은 양의 메모리를 필요로 할 수 있습니다.예를 들어 그래픽스 파일 프로세서는 이미지 파일의 전체 내용을 읽고 메모리에 저장하는 것으로 시작할 수 있습니다.이것은 매우 큰 이미지가 존재하지 않는 경우입니다.사용 가능한 메모리를 시드합니다.
바꿔 말하면, 메모리 누수는 특정 종류의 프로그래밍 에러로 인해 발생하며, 프로그램 코드에 액세스하지 않으면 메모리 누설이 있을 수 있다고 추측할 수 밖에 없습니다.이러한 내부 지식이 존재하지 않는 경우에는 "기억 사용률 계속 증가"와 같은 용어를 사용하는 것이 좋습니다.
C++의 간단한 예
다음 C++ 프로그램은 할당된 메모리에 대한 포인터를 손실함으로써 의도적으로 메모리를 리크합니다.
인트 주된() { 인트* a = 신규 인트(5); a = 특수; /* 'a'의 포인터가 존재하지 않기 때문에 해방할 수 없습니다. 메모리는 아직 시스템에 의해 할당되어 있습니다. 프로그램이 이러한 포인터를 해방시키지 않고 계속 작성하면, 지속적으로 메모리를 소비합니다. 따라서 누수가 발생할 수 있습니다.*/ }
「 」를 참조해 주세요.
- 버퍼 오버플로
- 메모리 관리
- 메모리 디버거
- Plumbr은 Java Virtual Machine에서 실행되는 애플리케이션에 널리 사용되는 메모리 누수 감지 도구입니다.
- nmon(Nigel's Monitor의 줄임말)은 AIX 및 Linux 운영 체제에서 널리 사용되는 시스템 모니터 도구입니다.
레퍼런스
- ^ Crockford, Douglas. "JScript Memory Leaks". Archived from the original on 7 December 2012. Retrieved 20 July 2022.
- ^ "Creating a memory leak with Java". Stack Overflow. Retrieved 2013-06-14.
- ^ Mitchell, Neil. "Leaking Space". Retrieved 27 May 2017.