본문 바로가기
[ Program ]/C#

.NET 메모리관리

by 관이119 2012. 9. 17.
.NET 메모리관리 (첫번째 이야기)

.NET이전에는 application에서 사용하는 메모리 관리에 대한 책임이 전적으로
application개발자에게 있었다. 그래서 사용한 메모리를 system에 돌려주지
않으면 system을 죽일 수 도 있는 굉장한(?) 프로그램을 만들 수도 있었다.

.NET에서는 모두 다는 아니지만 메모리 관리에 대한 굉장히 큰 부분을
CLR(Common Language Runtime)이라고 불리는 .NET실행환경이 나누어 갖는다.
그래서 .NET 응용프로그램은 .NET의 버그가 아닌이상 Memory Leak을
경험할 수 없다. 하지만 여전히 Object를 현명하게 사용하지 않으면 잘 못된
메모리 소비(Memory Consumption)로 인해 성능이나 안정성에 문제가
생길 수 있다.

흠... 왜 그럴까?

C/C++에서는 메모리를 할당하기 위해 malloc이나 new와 같은 함수나 키워드를
사용했다. 하지만 .NET 응용프로그램에서는 모든 object의 메모리 할당은
new를 통하여 일어난다.
이때 할당되는 object들은 모두 Managed Heap(관리되는 Heap이라고 쓴
책들도 있다)이라고 하는 공간에 생성된다.

.NET에서 메모리의 할당은 방에서 블록쌓기를 하는것과 같다고 할 수 있다.
(넓이는 무시하고 높이만 생각하자.)
블록을 계속 쌓다보면 천장에 닿아서 더 이상 쌓지 못 할 것이고 결국
천장을 뚫거나 이미 쌓여있는 블록중 마음에 안 드는 블록을 빼고 위에 공간을
확보하는 것이 마음에 드는 블록을 계속 쌓을 수 있는 방법일 것이다.

이런 일을 .NET CLR이 하게 된다. Managed Heap이라는 일정한 공간을 잡고
application에서 new를 통해 object를 할당할때 마다 차곡차곡 object size만큼
메모리를 할당한다. 그러다가 managed heap이 다 차게되면 지붕을 뚫기전에
마음에 안드는 블록(더 이상 참조되지 않는 Object)을 빼낸다. 그리고 중력에
의해 블록이 아래로 내려가듯이 살아남은 object들을 차례대로 재배치하는
작업을 하게된다. 이 과정을 .NET은 Gargabe Collection이라고 부른다.
이런 과정을 통해서도 새로운 object를 위한 공간이 확보되지 않으면 CLR은
managed heap을 늘려서(지붕을 뚫어서 ^^;) 필요한 공간을 확보하게 된다.

위에 설명한 이유때문에 별로 사용하는 Object도 없는 Hello World같은 크기가
작은 .NET application을 실행하더라도 Windows 작업관리자에서 응용프로그램이
사용하는 메모리의 양을 확인하면 기본적으로 대략 8Mb의 메모리 공간을 사용하는 것처럼 나온다.
이것이 바로 블록을 쌓기위한 방이 된다.
이렇게 .NET에서는 Object할당을 위한 공간을 미리 확보해 놓기 때문에 Object를 생성하는
작업이 굉장히 빠르게 된다.

그럼 Object생성은 그렇다치고 CLR은 어떻게 Object가 더 이상 필요없는지를
판단하는것일까?

새로운 object를 할당하려고 하는데 더 이상 공간이 없을때 GC가 수행된다고
했다. 이때 CLR은 Managed Heap내에 있는 모든 object를 garbage라고
가정한다. 그리고 Strong Reference라고도 불리는 Application Roots로 부터
각 Object들이 서로 어떻게 참조하고 있는지를 다시 확인하게 된다.
Application Roots는 Global, Local, Static, Parameter등으로 사용되고 있는
Object들의 pointer를 가지고 있게 되며 물론, CLR이 관리한다.
이렇게 Application Roots로 부터 새로운 참조 그래프가 그려지고 나면
사용되고 있는 object와 사용되고 있지 않은 object가 가려지고 CLR은
예전 Win9x에서 조각모음을 하듯 참조 그래프에 있는 object들만을 Managed
Heap의 처음부터 차례대로 복사해 놓는다. 그리고 application roots에서
참조되고 있는 메모리 주소를 바꾸고 나면 managed heap에 마지막 object가
있는 다음부터는 free space가 되는 것이다. 그리고 이렇게 항상 다음 새로운
object가 생성될 pointer를 CLR이 관리하게 된다.

object생성과는 다르게 object를 메모리에서 해제하는 과정은 굉장히 시스템
자원을 많이 요구하는 작업이다. 특히나 managed heap의 크기가 크고
생성된 object가 많다면 참조 그래프를 그리고 메모리를 복사하는 작업이
더 오래 걸릴것은 당연한 사실이다.
그래서 Garbage Collection에는 Generation이라는 개념이 들어있다.

일단 여기까지 하고 Generation은 졸리니까 나중에 하자. --zZzZ

NET 메모리관리 (두번째 이야기)

1000cc맥주를 마신다고 가정해 보자.

이 맥주를 한번도 쉬지않고 다 마시려면 굉장히 힘이 들것이다.
(물론 힘 안들이고 다 마시는 사람을 본적은 있다... --;;;)

그럼 맥주를 기분좋게 즐겁게 마시는 방법은 무엇일까?

당연히 한 모금씩 내지는 적당한 양만큼 나누어서 마시는 것이

좋을 것이다.

.NET의 메모리 관리에도 이런(예가 적절한지는 모르겠지만... --;) 방법으로

Garbage Collection을 한다.

무신 얘긴고 하면

앞서 설명했듯이 .NET에서는 모든 object들이 Managed Heap이라는 공간에

생성이 되며 더 이상 object들을 할당한 공간이 없게 되면 Garbage Collection이

일어나게 되고 이때 Application Root로 부터 그려지는 참조 그래프에 존재하지

않게되면 garbage로 간주되어 메모리에서 사라지게 된다. 이렇게해서도 필요한

메모리가 확보되지않으면 Managed Heap이 늘어나게 된다.

결국 이 얘기는 Managed Heap이 늘어날수록 CLR이 한번에 처리해야되는 메모리의 양이

늘어난다는 얘기이다. 이 경우 여러분이 만든 application의 성능이 저하 될 수 있다.

왜냐하면 Garbage Collection이 일어나는 동안은 object참조 그래프를

그리고 object의 위치를 옮기는 등의 작업을 하기위해 CLR이 모든 thread들을

세워놓기(suspend상태로 만들기) 때문이다.

결국 사용하는 object가 많아질수록(managed heap이 커질수록) CLR이

Garbage Collection에 들이는 시간도 커진다.

그럼 이런 문제를 해결할 수 있는 방법은 무엇일까?

그렇다. 나누어 마시는 것이다.

그래서 CLR에는 Generation이라는 개념이 들어있다.

Generation은 여러 문서들에 보면 Gen0에서 시작해서 Gen1, Gen2로 Object들이

Garbage Collection에서 살아남을때 마다 옮겨가는 과정이라고 나와있다.

이 이야기는 결국 Object가 Garbage Collection을 당한 횟수라고 보면된다.

새로 생성된 object는 GC가 된적이 없으므로 Gen0 이고 그 이후에 GC가 일어났는데

garbage가 아니어서 살아남았다면 GC가 1번 일어났으므로 Gen1 이 되는 것이다.

마찬가지로 Gen1에서 한번더 GC가 일어났을때 살아남으면 Gen2가 되는 것이다.
(Gen2가 마지막 Generation이다.)

이렇게 Generation을 나누므로써 CLR은 맥주를 나누어 마실 수 있게 된다.

managed heap에 더 이상 object를 할당할 공간이 없게 되면 일단 Gen0 object들을 대상으로

GC를 수행한다. 그래서 필요한 공간이 확보되면 나머지 Generation에 대해서는

GC가 수행되지 않는것이다. 이렇게 함으로써 managed heap이 커져도 CLR은

GC를 보다 효율적으로 수행할 수 있는 것이다.
http://zplanet.egloos.com/ 발췌

 

댓글