일요일 별로 할일도 없는김에 포스팅이나 하나 더 올린다.
.NET Framework에서 가비지콜렉터의 작동방식을 얘기하면서,
덤으로 .NET Compact Framework쪽도 언급한다.

힙메모리에 생성시킨 객체는 가비지컬랙터의 관리대상이 된다.
NagarryClass라는 클래스가 있다고 가정하고,
객체를 생성해서 힙메모리에 올려보자.
아래와 같이 하면 된다.
NagarryClass nagarry = new NagarryClass();

이렇게 하면 nagarry라는 객체가 생성되었고,
NagarryClass에서 정의했던 만큼 힙메모리 영역을 할당시킬 수 있다.
.NET에서는 이 메모리영역을 Managed Heap이라고 부르고있다.

.NET Framework의 Managed힙은 삼세대(3 generation)로 이루어져있고,
.NET Compact Framework에서는 한세대만 존재한다.

(Windows Mobile이나 Xbox는 .NET Compact Framework으로 되어있으므로, 그쪽에 관심있는 어린이는 한세대만으로 작동하는 가비지콜랙터에 대해 좀더 알아두는 것이 신상에 좋을 것이다.)

첫째세대에는 최근 할당된 메모리들이 저장된다.
이 곳에 있는 메모리는 NULL로 설정되면 비활성상태로 마킹된다.
프로그램이 구동되다가 이곳이 가득차면 가비지콜랙터가 작동하게 되는데,
가비지콜랙터는 아직 활성상태인 메모리를 둘째세대로 넘긴다음, 첫째세대의 모든 메모리를 반환시킨다.
둘째세대에는 첫째세대에서 할당되었다가 아직 반환되지 않는 메모리들이 저장된다.
활동시간이 비교적 긴 객체들인 것이다.
첫째세대에서와 마찬가지로 이곳 역시 가득차게 되면 가비지콜랙터에 의해
샛째세대로 메모리들이 이동하게 된다.
셋째세대에는 활동시간이 아주 긴 객체의 메모리들만 저장되게 된다.
마지막 세대인 이곳마저 가득차게 된다면...
가비지콜랙터는 Full가비지콜랙션을 수행하는데,
힙 전체 메모리영역을 싹~ 점검해서 활성/비활성을 판단하고,
비활성이면 반환하는 작업이 진행되기 때문에 무거운 연산일 수 밖에 없다.

Full가비지컬랙션이란 것이 발생하면 CPU의 처리능력이 일시적으로 떨어질 수밖에 없다.
여기에 Finalizer까지 호출해야하는 객체가 몇개 끼어있다면 CPU의 성능은 바닥으로 떨어질 것이다.
Finalizer가 왜 CPU성능을 떨어트리는지는 언급해보자면...
가비지콜랙터는 객체에 Finalize메서드가 있다면 힙에 있는 메모리가 비활성 상태로
표시되기 이전에 객체를 별도의 큐로 이동시켜야만 한다.
그곳에는 추가적인 메모리 반환작업이 필요하기 때문이다.
큐로 이동시킨 후에야 가비지콜랙터가 다시 작동할때 종료대상큐를 통해 메모리를 반환할 수 있다.
그래서 많은 .NET전문가들이 Finalize메서드는 사용하지 말라고 충고하는 것이다.
물론 Unmanaged 객체를 사용하지 않는다면 Finalize메서드는 전혀 사용할 필요가 없지만 말이다 ^^


추가로 알아야할 것은
새로 생성되는 객체에 너무나 큰 메모리가 필요하다면...
곧장 셋째세대에 저장되어버릴 수 있다는 거다.
첫째세대에 담을 수 없는 크기라면 곧장 셋째세대로 넘겨버림으로서,
대용량의 메모리 할당과 반환작업이 두세대에 걸쳐 반복적으로 일어나는 것을 막기위함이다.


Managed힙이 삼대로 구성된 것은 어디까지나 .NET Framework이고,
.NET Compact Framework은 한세대만으로만 이루어져있다.
첫째,둘째없이 셋째세대만 존재하는 것이다.
가비지콜랙터는 기본적으로 .NET Framework의 셋째세대가 하는 것과 동일한 역할을 한다.
.NET Compact Framework의 가비지콜랙터가 추가적으로 하는 일은 힙조각모음과 코드파기이다.

.NET Compact Framework의 가비지콜랙터는 주기적으로 힙영역을 조사해서
영역들이 너무 조각나 있다고 판단되면 힙영역을 빈공간없이 채우는 작업을 수행한다.
그래도 새로운 객체가 들어설 빈공간이 없다면 컴파일되었던 코드마저 파기해서 힙영역을 넓힌다.
한세대만 존재하는 .NET Compact Framework의 고육지책인 것이다.
이로 인해 파기된 코드들은 다시 컴파일되어야 하므로 CPU는 더욱 부담을 가질 것이다.



결국 "결론은 버킹검"이라고...
우찌됐든 다쓴 객체는 null로 바로바로 반환해주고,
클래스 디자인할때 너무많은 맴버들과 객체를 참조하게 하지말고,
(객체 활동주기가 긴건도 봐줄 수 있고, 큰것도 봐줄수는 있다. 그렇지만 크고 오래남아있는 객체는 봐 줄 수 없다!)
가능하면 제네릭타입을 사용해서 객체의 사본생성을 최소화하는게 좋다.

오늘은 여기까지~

Google AdSense

간만에 블로깅이다.
오늘은 .NET Framework의 가비지콜랙터에 대해 끄적여볼껀데,
가비지콜랙터, 메모리누수에 대해 눈꼽만큼이라도 고민해 본적이 있는 어린이들에게 도움이 됐으면 한다.

※여기서 [객체], [인스턴스변수], [포인터]는 모두 같은 의미이다.※


C나 C++에서는 힙 메모리를 할당한 후 그것을 관리하던 포인터를 잃어리면...
해당 쓰레드가 종료되기 전까지 세어나간 메모리를 돌려놓을 방법이 없다.
이것이 바로 일반적으로 메모리누수라고 부르는 것이다.

엄밀히 말하자면...
Java, C#으로 넘어오면서 프로그래머들은 메모리 누수로 부터 해방됐다.
바로 가비지콜랙터라는 놈이 힙 메모리를 자동으로 반환해주기 때문이다.

그럼에도 불구하고 아직도 우리는 메모리누수라는 말을 가끔 쓰고 있다.
그럼 Java나 C#을 사용하면서 얘기하는 메모리누수라는 것은 무엇이냐?
.....
여기서 메모리누수라는 것은.... 반환되어야할 시기에 반환되지 않는 메모리를 얘기하는 것이다.

가비지콜랙터는 객체(포인터, 인스턴스변수)가 NULL로 설정되면 사용하던 힙메모리를 자동으로 반환해준다.
물론 NULL을 명시적으로 주지 않아도,
함수내에서 생성했던 객체들은 함수의 활동이 끝나서 스택에서 퇴출될때 함께 반환된다.
(알아둬야 할 것은 NULL로 설정 되었다고해서 가비지콜랙터가 즉시 반환해주지는 않는다. 언젠가는 반환해주지만, 가비지콜랙터가 자주 출동하면 시스템이 느려진다. 이건 나중에 따로 포스팅을 해볼 생각이다.)

어쨋든 포인터를 NULL로 설정하지 않으면 해당 프로세스, 쓰레드, 함수가 종료되기 전까지는
가비지콜랙터가 메모리를 반환할 방법이 없다.
이런 것들이 쌓이는 것이 여기서 얘기하는 메모리누수인데....
가장 흔한 경우가 바로 활동시간이 아주 기~인 객체가 여러 객체들과 짝지어진 경우이다.
한두번 쓰이고 반환되어야할 많은 객체들이 활동시간이 긴 객체와 함께 오래오래 살아남아 문제가 된다.
자신을 참조하는 객체가 계속 살아있으니, 참조당하는 객체들이 NULL로 될수 없고,
NULL이 아닌 객체들을 가비지콜랙터가 반환할 수는 없으니 말이다.

따라서 메모리누수를 막기위해서는
첫째, 활동기간이 긴 객체와 짧은 객체는 되도록 참조하는 것을 피하고,
둘째, 사용이 끝난 포인터는 즉시 NULL로 할당해주는 코딩습관이 필요하다.
  괜히 순환문이 있는 길고 긴 함수가 종료되거나, 호출객체의 활동이 끝때까지 그대로 놔두면...
  언젠가 큰 화를 불러올지도 모른다.
셋째, Full 가비지콜랙터가 출동하는 것을 막는 것이 좋다.
  흠, 이건 메모리누수보단 Full 가비지콜랙터의 작동때문에 시스템이 느려지는 것을 막기위함인데...
  활동기간이 긴 객체는 최소한의 메모리만 사용하도록 하는 것이 좋다.

Google AdSense

+ Recent posts