메모리 관리되지 않는 프로그래밍의 복잡성은 무엇입니까? 확보가 얼마나 복잡한 지 알 수 없습니다. GC가

다시 말해서, 자동 가비지 콜렉션은 어떤 특정 문제를 해결 했습니까? 저수준 프로그래밍을 한 번도 해본 적이 없으므로 리소스 확보가 얼마나 복잡한 지 알 수 없습니다.

GC가 해결하는 버그의 종류는 (적어도 외부 관찰자에게는) 자신의 언어, 라이브러리, 개념, 관용구 등을 잘 아는 프로그래머가하지 않는 종류의 것으로 보입니다. 그러나 나는 틀릴 수있다 : 수동 메모리 처리는 본질적으로 복잡합니까?



답변

저수준 프로그래밍을 한 번도 해본 적이 없으므로 리소스 확보가 얼마나 복잡한 지 알 수 없습니다.

“낮은 수준”의 정의가 시간이 지남에 따라 어떻게 변하는 지 재미있다. 프로그래밍을 처음 배웠을 때 간단한 할당 / 무료 패턴을 가능하게하는 표준화 된 힙 모델을 제공하는 모든 언어는 실제로 높은 수준으로 간주되었습니다. 에서 낮은 수준의 프로그래밍 , 당신은 메모리 자신의 트랙을 계속해야 (안 할당을하지만, 메모리 위치 자체!), 또는 당신이 정말로 멋진 느낌을한다면 자신의 힙 할당을 쓸 것입니다.

말했듯이, 실제로 그것에 대해 무섭거나 “복잡한”것은 전혀 없습니다. 당신이 어렸을 때 엄마가 장난감을 가지고 놀았을 때 장난감을 치워달라고 말한 것을 기억하십시오. 메모리 관리는 코드에 적용되는 것과 동일한 원칙입니다. (GC는 당신 청소 하는 하녀를 갖는 것과 같지만, 그녀는 매우 게으르고 약간의 단서가 없습니다.) 그 원리는 간단합니다. 코드의 각 변수에는 한 명의 소유자 만 있고, 그 소유자의 책임입니다 더 이상 필요하지 않은 변수의 메모리를 비 웁니다. ( 단일 소유권 원칙) 할당 당 한 번의 호출이 필요하며, 소유권 및 정리를 자동화하는 여러 가지 방법이 있으므로 해당 호출을 자신의 코드에 작성할 필요도 없습니다.

가비지 콜렉션은 두 가지 문제점을 해결해야합니다. 그것은 그들 중 하나에서 항상 매우 나쁜 일을하며, 구현에 따라 다른 것과 잘 맞지 않을 수도 있습니다. 문제는 메모리 누수 (완료된 후 메모리 유지)와 댕글 링 참조 (완료되기 전에 메모리 비우기)입니다. 두 가지 문제를 모두 살펴 보겠습니다.

매달려있는 참고 자료 :이 문제는 실제로 심각한 문제이므로 먼저 토론하십시오. 동일한 객체에 대한 두 개의 포인터가 있습니다. 당신은 그들 중 하나를 해제하고 다른 하나를 눈치 채지 못합니다. 그런 다음 나중에 두 번째 내용을 읽거나 쓰거나 해제하려고 시도합니다. 정의되지 않은 동작이 발생합니다. 눈치 채지 못하면 메모리가 쉽게 손상 될 수 있습니다. 가비지 콜렉션은 모든 참조가 사라질 때까지 아무것도 해제되지 않도록하여이 문제점을 불가능하게해야합니다. 완전히 관리되는 언어에서는 관리되지 않는 외부 메모리 리소스를 처리해야 할 때까지 거의 작동합니다. 그런 다음 바로 1로 돌아갑니다. 관리되지 않는 언어에서는 상황이 여전히 까다로워집니다. (모질라를 돌아 다니며

다행히이 문제를 다루는 것은 기본적으로 해결 된 문제입니다. 가비지 수집기가 필요하지 않으며 디버깅 메모리 관리자 가 필요합니다 . 예를 들어 Delphi를 사용하고 단일 외부 라이브러리와 간단한 컴파일러 지시문을 사용하여 할당자를 “Full Debug Mode”로 설정할 수 있습니다. 이는 사용 된 메모리를 추적하는 일부 기능을 활성화하기 위해 무시할 수있는 (5 % 미만) 성능 오버 헤드를 추가합니다. 객체를 비우면 메모리가 채워집니다.0x80바이트 (디버거에서 쉽게 인식 가능)를 해제하고 해제 된 객체에서 가상 메소드 (소멸자를 포함)를 호출하려고하면 객체가 생성 될 때 3 개의 스택 추적이있는 오류 상자가있는 프로그램을 발견하고 중단합니다. 그것이 해방되었을 때, 그리고 내가 지금 어디에 있는지 – 다른 유용한 정보와 더불어 예외를 일으킨다. 이것은 분명히 릴리스 빌드에는 적합하지 않지만 매달려있는 참조 문제를 추적하고 수정하는 것은 쉽지 않습니다.

두 번째 문제는 메모리 누수입니다. 더 이상 필요하지 않을 때 할당 된 메모리를 계속 유지할 때 발생합니다. 가비지 수집 유무에 관계없이 모든 언어로 발생할 수 있으며 코드를 올바르게 작성해야만 해결할 수 있습니다. 가비지 콜렉션은 아직 해제되지 않은 메모리 조각에 대한 유효한 참조가 없을 때 발생하는 특정 유형 의 메모리 누수 를 완화하는 데 도움이됩니다. 즉, 프로그램이 끝날 때까지 메모리가 할당 된 상태로 유지됩니다. 불행히도, 자동화 된 방식으로이 작업을 수행하는 유일한 방법은 모든 할당을 메모리 누수로 전환하는 것입니다!

그런 말을하려고하면 아마도 GC 지지자들에 의해 찌그러 질 것입니다. 메모리 누수의 정의는 더 이상 필요하지 않은 경우 할당 된 메모리를 유지합니다. 무언가에 대한 참조가없는 것 외에도, 해제해야 할 때 컨테이너 객체에 메모리를 보관하는 등 불필요한 참조를가함으로써 메모리가 누출 될 수 있습니다. 이 작업으로 인한 메모리 누수를 보았습니다. GC가 있는지 여부를 추적하는 것은 매우 어렵습니다. 메모리에 대한 완벽한 유효한 참조가 포함되어 있으며 디버깅 도구에 대한 명확한 “버그”가 없기 때문입니다. 잡기. 내가 아는 한, 이러한 유형의 메모리 누수를 잡을 수있는 자동화 된 도구는 없습니다.

따라서 가비지 수집기는 참조되지 않은 다양한 메모리 누수와 관련이 있습니다. 자동화 된 방식으로 처리 할 수있는 유일한 유형이기 때문입니다. 그것이 모든 참조에 대한 모든 참조를보고 그것을 가리키는 참조가없는 즉시 모든 객체를 자유롭게 할 수 있다면, 적어도 참조없는 문제와 관련하여 완벽 할 것입니다. 이를 자동화 된 방식으로 수행하는 것을 참조 카운팅 이라고 하며 일부 제한된 상황에서 수행 할 수 있지만 처리해야 할 자체 문제가 있습니다. (예를 들어, 개체 A에 대한 참조를 보유하는 개체 B에 대한 참조를 보유한 개체 A. 참조 계산 체계에서 A 또는 B에 대한 외부 참조가없는 경우에도 개체를 자동으로 해제 할 수 없습니다.) 가비지 수집기 사용 추적대신 : 시작 알려진 좋은 개체의 집합으로하는 모든 객체를 찾아, 그들이 참조하는 모든 오브젝트를 찾을 그들이 참조하고, 그래서 당신은 모든 것을 발견했습니다 재귀 때까지. 추적 프로세스에서 발견되지 않은 것은 쓰레기이므로 버릴 수 있습니다. (물론 이것을 성공적으로 수행하려면 추적 가비지 수집기가 항상 포인터처럼 보이는 임의의 메모리 조각과 참조 간의 차이를 항상 알 수 있도록 형식 시스템에 특정 제한을 적용하는 관리되는 언어가 필요합니다.)

추적에는 두 가지 문제점이 있습니다. 첫째, 속도가 느리고 진행되는 동안 경쟁 조건을 피하기 위해 프로그램이 다소 일시 중지되어야합니다. 이로 인해 프로그램이 사용자와 상호 작용하거나 서버 응용 프로그램의 성능이 저하 될 때 눈에 띄는 실행 딸꾹질이 발생할 수 있습니다. 할당을 처음 시도 할 때 할당이 수집되지 않으면 잠시 동안 고착 될 수 있다는 원칙에 따라 할당 된 메모리를 “세대”로 분할하는 등의 다양한 기술로이를 완화 할 수 있습니다. .NET 프레임 워크와 JVM 모두 세대 가비지 콜렉터를 사용합니다.

불행히도, 이것은 두 번째 문제로 들어갑니다. 메모리를 다 사용하면 메모리가 해제되지 않습니다. 객체로 작업을 마친 직후에 추적을 실행하지 않으면 다음 추적까지, 또는 1 세대를 지나서 더 길게 추적 될 수 있습니다. 사실, 내가 본 .NET 가비지 수집기의 가장 좋은 설명 중 하나 는 프로세스를 최대한 빨리 만들려면 GC가 가능한 한 오랫동안 수집을 연기해야한다고 설명합니다! 따라서 메모리 누수 문제는 가능한 한 오래 동안 가능한 많은 메모리를 누수 함으로써 기괴하게 “해결됩니다” ! 이것은 GC가 모든 할당을 메모리 누수로 바꾼다는 것을 의미합니다. 사실, 주어진 객체가된다는 보장이 없다 지금까지 수집.

필요할 때 메모리가 계속 재생 될 때 이것이 왜 문제입니까? 몇 가지 이유가 있습니다. 먼저, 많은 양의 메모리를 차지하는 큰 객체 (예 : 비트 맵)를 할당한다고 가정하십시오. 그런 다음 곧 완료되면 같은 양의 메모리를 사용하는 또 다른 큰 객체가 필요합니다. 첫 번째 개체가 해제되면 두 번째 개체는 메모리를 재사용 할 수 있습니다. 그러나 가비지 수집 시스템에서는 여전히 다음 추적이 실행되기를 기다리는 중이므로 두 번째 큰 객체에 불필요하게 메모리를 낭비하게됩니다. 기본적으로 경쟁 조건입니다.

둘째, 특히 대량으로 메모리를 불필요하게 보유하면 최신 멀티 태스킹 시스템에서 문제가 발생할 수 있습니다. 실제 메모리를 너무 많이 차지하면 프로그램이나 다른 프로그램이 페이지를 이동 (메모리의 일부를 디스크로 교체해야 함)하여 실제로 속도가 저하 될 수 있습니다. 서버와 같은 특정 시스템의 경우 페이징은 시스템 속도를 늦출뿐만 아니라 로드 상태 인 경우 전체를 중단시킬 수 있습니다.

댕글 링 참조 문제와 마찬가지로, 참조없는 문제는 디버깅 메모리 관리자로 해결할 수 있습니다. 필자는 Delphi의 FastMM 메모리 관리자에서 Full Debug Mode를 언급 할 것입니다. 가장 친숙하기 때문입니다. (다른 언어에서도 비슷한 시스템이 존재합니다.)

FastMM에서 실행중인 프로그램이 종료되면 해제되지 않은 모든 할당의 존재를 선택적으로보고하도록 할 수 있습니다. 전체 디버그 모드는 한 단계 더 나아갑니다. 할당 된 유형뿐만 아니라 할당 된 각 스택에 대한 스택 추적 및 기타 디버그 정보를 포함하는 디스크에 파일을 저장할 수 있습니다. 이렇게하면 참조없는 메모리 누수를 추적하는 것이 쉽지 않습니다.

실제로 보면 가비지 수집은 매달려있는 참조를 방지하는 데 도움이 될 수도 있고 그렇지 않을 수도 있으며 일반적으로 메모리 누수 처리에서 나쁜 작업을 수행합니다. 실제로 한 가지 미덕은 가비지 수집 자체가 아니라 부작용입니다. 힙 압축을 자동으로 수행하는 방법을 제공합니다. 이렇게하면 오랫동안 계속 실행되고 높은 수준의 메모리 변동이있는 프로그램을 중단시킬 수있는 가혹한 문제 (힙 조각화를 통한 메모리 소진)를 방지 할 수 있으며 가비지 수집없이 힙 압축이 거의 불가능합니다. 그러나 요즘 좋은 메모리 할당자는 버킷을 사용하여 조각화를 최소화하므로 극단적 인 상황에서는 조각화가 실제로 문제가됩니다. 힙 조각화가 문제가 될 수있는 프로그램의 경우 압축 가비지 수집기를 사용하는 것이 좋습니다. 그러나 다른 경우에 IMO는 가비지 수집을 사용하는 것이 조기 최적화이며 “해결하는”문제에 대한 더 나은 솔루션이 존재합니다.


답변

C ++의 RAII와 같이 현재 널리 사용되는 시스템에서 사용되는 가비지 수집기와 같은 시대의 비가 비지 수집 메모리 관리 기술을 고려합니다. 이 접근 방식을 사용하면 자동화 된 가비지 콜렉션을 사용하지 않는 비용이 최소화되며 GC는 자체 문제점을 많이 발생시킵니다. 따라서, “별로”가 귀하의 문제에 대한 답변이라고 제안합니다.

사람들이 비 GC 생각하면, 그들은 생각, 기억 malloc하고 free. 그러나 이것은 거대한 논리적 오류입니다. 1970 년대 초 비 GC 리소스 관리를 90 년대 후반의 가비지 수집기와 비교할 것입니다. 이것은 분명히 사용에있을 때 쓰레기 수집 다소 불공정 comparison-입니다 mallocfree설계되었다 내가 제대로 기억한다면, 의미있는 프로그램을 실행하는 데 너무 느렸다. 예를 들어 unique_ptr, 모호하게 동등한 기간과 무언가를 비교하는 것이 훨씬 더 의미가 있습니다.

가비지 콜렉터는 참조주기를보다 쉽게 ​​처리 할 수 ​​있지만 이는 드문 경험입니다. 또한 GC는 모든 메모리 관리를 처리하므로 개발주기가 빨라질 수 있기 때문에 코드를 “던지기”할 수 있습니다.

반면, 자체 GC 풀을 제외한 다른 곳에서 온 메모리를 처리 할 때 엄청난 문제가 발생하는 경향이 있습니다. 또한 동시성을 사용하면 객체 소유권을 고려해야하기 때문에 많은 이점을 잃게됩니다.

편집 : 언급 한 많은 것들이 GC와 관련이 없습니다. 메모리 관리와 객체 지향이 혼란 스럽습니다. C ++과 같은 완전히 관리되지 않는 시스템에서 프로그래밍하는 경우 원하는만큼 범위 검사를 수행 할 수 있으며 표준 컨테이너 클래스가 제공합니다. 예를 들어 바운드 검사 또는 강력한 타이핑과 같은 GC는 없습니다.

언급 한 문제는 GC가 아닌 객체 지향으로 해결됩니다. 배열 메모리의 기원과 외부에 쓰지 않도록하는 것은 직교 개념입니다.

편집 : 고급 기술을 사용하면 모든 형태의 동적 메모리 할당이 필요하지 않습니다. 예를 들어, 동적 할당이 전혀없는 C ++에서 Y 조합을 구현하는 this 의 사용을 고려하십시오 .


답변

가비지 수집 언어가 제공 할 것으로 예상되는 “자원 확보에 대해 걱정할 필요가없는 자유”는 상당 부분 환상입니다. 아무 것도 제거하지 않고 계속지도에 물건을 추가하면 곧 내가 말하는 것을 이해할 수 있습니다.

실제로 GCed 언어로 작성된 프로그램에서 메모리 누수는 매우 빈번합니다. 이러한 언어는 프로그래머를 게으르게 만들고 언어가 항상 (매직) 모든 객체를 처리한다는 잘못된 보안 감각을 갖기 때문입니다. 더 이상 생각할 필요가 없습니다.

가비지 콜렉션은 다른 고귀한 목표를 가진 언어에 필요한 기능입니다. 모든 것을 객체에 대한 포인터로 취급하고 동시에 프로그래머가 포인터라는 사실을 프로그래머로부터 숨겨 프로그래머가 커밋 할 수 없습니다. 포인터 산술 등을 시도하여 자살. 객체가되는 것은 GCed 언어가 GC가 아닌 언어보다 훨씬 더 자주 객체를 할당해야 함을 의미합니다. 즉, 프로그래머가 해당 객체를 할당 해제해야하는 부담이 가해지면 엄청나게 매력적이지 않습니다.

또한 가비지 수집은 프로그래머가 모든 코드를 할당 해제하기 위해 표현식을 별도의 명령문으로 나눌 필요없이 함수형 프로그래밍 방식으로 코드 내에서 타이트한 코드를 작성하고, 표현식 내에서 오브젝트를 조작 할 수있는 기능을 제공하는 데 유용합니다. 표현식에 참여하는 단일 객체.

이 모든 것 외에, 나는 나의 대답의 시작 부분에 “그것은 상당히 환상이다” 라고 썼음을 주목하시기 바랍니다 . 나는 그것이 환상 이라고 쓰지 않았습니다 . 나는 그것이 대부분 환상 이라고 쓰지 않았습니다 . 가비지 콜렉션은 프로그래머가 자신의 객체를 할당 해제하는 데 따르는 정신적 인 과제를 제거하는 데 유용합니다. 따라서 이런 의미에서 생산성 기능입니다.


답변

가비지 콜렉터는 “버그”를 해결하지 않습니다. 일부 고급 언어 의미의 필수 부분입니다. GC를 사용하면 어휘 폐쇄와 같은 높은 수준의 추상화를 정의 할 수 있지만 수동 메모리 관리에서는 이러한 추상화가 유출되어 불필요하게 낮은 수준의 자원 관리에 바인딩됩니다.

의견에 언급 된 “단일 소유권 원칙”은 이러한 유출이 발생한 추상화의 좋은 예입니다. 개발자는 특정 기본 데이터 구조 인스턴스에 대한 링크 수에 전혀 신경을 쓰지 않아야합니다. 그렇지 않으면 코드가 직접 추가 할 수없는 수많은 추가 (코드 자체에 표시되지 않음) 제한 및 요구 사항이 없으면 코드가 일반적이고 투명하지 않습니다. . 이러한 코드는 더 높은 수준의 코드로 구성 될 수 없으며, 이는 책임 분리 원칙 (소프트웨어 엔지니어링의 주요 구성 요소 인 불행히도 대부분의 저수준 개발자가 전혀 존중하지 않는)의 계층을 어길 수없는 위반입니다.


답변

실제로, 자신의 메모리를 관리하는 것은 하나의 잠재적 인 버그의 원인 일뿐입니다.

호출을 잊어 버린 경우 free(또는 사용중인 언어에 해당하는 언어가 무엇이든) 프로그램은 모든 테스트를 통과하지만 메모리가 누출 될 수 있습니다. 그리고 약간 복잡한 프로그램에서는에 대한 호출을 간과하기 쉽습니다 free.


답변

수동 리소스는 지루할뿐만 아니라 디버깅하기도 어렵습니다. 다시 말해서, 그것을 올바르게 얻는 것이 지루할뿐만 아니라, 잘못했을 때 문제가 어디에 있는지 분명하지 않습니다. 이는 예를 들어 0으로 나누는 것과 달리 오류의 영향이 오류의 근원에서 멀어지고 점을 연결하는 데 시간,주의 및 경험이 필요하기 때문입니다.


답변

가비지 수집은 하나의 큰 진보의 물결이 아닌 GC와 관련이없는 언어 개선에 대해 많은 신용을 얻는다고 생각합니다.

내가 아는 GC의 확실한 장점 중 하나는 프로그램에서 객체를 자유롭게 설정하고 모든 사람이 객체를 다 사용할 때 사라질 것이라는 것을 알고 있다는 것입니다. 다른 클래스의 메소드로 전달할 수 있으며 걱정하지 않아도됩니다. 전달되는 다른 메소드 또는 참조하는 다른 클래스는 중요하지 않습니다. (메모리 누수는 객체를 생성 한 클래스가 아니라 객체를 참조하는 클래스의 책임입니다.)

GC가 없으면 할당 된 메모리의 전체 수명주기를 추적해야합니다. 주소를 생성 한 서브 루틴에서 주소를 올리거나 내릴 때마다 해당 메모리에 대한 제어 불능 참조가 있습니다. 예전에는 한 개의 스레드만으로도 재귀와 운영 체제 (Windows NT)만으로도 할당 된 메모리에 대한 액세스를 제어 할 수 없었습니다. 모든 참조가 지워질 때까지 한동안 메모리 블록을 유지하기 위해 자체 할당 시스템에서 무료 방법 을 조작해야 했습니다. 유지 시간은 순수한 추측이지만 효과가있었습니다.

이것이 내가 아는 유일한 GC 혜택이지만, 그것 없이는 살 수 없었습니다. 나는 어떤 종류의 OOP도 그것 없이는 날지 않을 것이라고 생각합니다.