E D R , A S I H C RSS

쓰레기 수집

last modified: 2015-03-28 22:06:28 by Contributors

Garbage Collection, GC. 영어를 그대로 읽어 가비지 컬렉션이라고도 부른다.

Contents

1. 개요
2. 동작 원리 기초

1. 개요


메모리 관리 방법 중 하나로, 프로그래머가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내어 해제하는 기능이다. 존 매카시가 1959년에 LISP의 메모리 관리를 위해 처음 만들었다고 알려져 있다.

옛날의 언어들은 FORTRAN이나 BASIC처럼 동적인 메모리 할당 기능이 아예 없거나(...)[1] C처럼 프로그래머가 할당한 뒤 수동으로 해제까지 해 줘야 하는 방식이었는데, 사람이 하는 일이 항상 완벽할 수는 없는지라 메모리를 할당해놓고 필요없어진 뒤에도 해제를 안 해서 메모리 누수가 생기거나, 혹은 거꾸로 해제했던 메모리를 실수로 다시 사용하거나, 해제했던 메모리를 또 해제한다거나 하는 온갖 실수가 일어나 수많은 버그가 양산되곤 했다. 더욱이, 일반적으로 버그는 재현가능하고 오류가 있는 부분으로부터 가까운 곳에서 터져야 잡기 쉬운데, 메모리 관련 버그는 한참 떨어진 곳에서 터지는 데다가 재현불가능한 경우도 있어서 프로그래머에게 지옥같은 디버깅을 선사해준다.

쓰레기 수집을 지원하는 환경에서는 프로그래머가 직접 메모리를 접근하지 못하게 막는 대신 쓰레기 수집기(garbage collector)가 관리할 수 있는 방법으로 메모리 영역을 할당해준다. 그리고 새 메모리 영역을 할당해줄 수 없을 정도로 메모리를 많이 사용했다고 판단되면 쓰레기 수집기가 작동해서 쓰이지 않는 메모리 영역을 전부 찾아 해제하게 된다. 이 때, 쓰레기 수집기가 메모리를 임의로 정리해버릴 수 있기 때문에 쓰레기 수집기가 동작하는 동안에는 보통 프로그램 실행이 일시 중지되며, 쓰레기 수집기의 성능이 좋지 못하거나 탐색해야 할 메모리 영역이 너무 많거나 하는 경우에는 프로그램이 눈에 띌 정도로 오래 멈춰 있을 수도 있다.

한편 꼭 필요한 경우 완전한 비동기 GC를 만드는 것도 가능하기는 하다. Erlang의 가상레지스터인 BEAM의 경우가 그러한데, 메모리를 마이크로 프로세스라는 작업 단위로 쪼개서 할당하고 각 마이크로 프로세스 사이에 공유메모리를 엄격하게 통제하고 모든 변수에 불변성을 주어서 관리비용을 최대한 줄인 결과 하드웨어적인 가용자원만 확보되면 GC의 작동이 프로그램을 중단시키는 일이 없어지도록 만들었다. 다만 이건 매우 극단적인 경우로 GC로 인한 속도변화가 없어지는 대신 전체적인 속도에서 손해를 본다[2]. 결국 GC자체는 어떻게 만들어도 비교적 비싼작업이라는 소리다.

이것은 쓰레기 수집 방식의 가장 큰 단점 중 하나였으나, 요즘은 쓰레기 수집기의 성능이 많이 좋아지고, 컴퓨터 성능은 그것보다 훨씬 많이 좋아졌으며, 결정적으로 쓰레기 수집이 되면 프로그래밍하기가 훨씬 쉬워지고 버그 발생률도 낮아지기 때문에 최근에 등장하는 언어들은 거의 대부분 쓰레기 수집 기능을 기본으로 탑재하고 나온다.

JavaC#이 언어 및 가상머신 차원에서 쓰레기 수집을 지원하며, Python이나 Ruby, Perl 등의 스크립트 언어들도 대부분 지원한다. 심지어 GoC처럼 시스템 프로그래밍을 지향함에도 불구하고 쓰레기 수집 기능을 쓴다! C++도 최근에 shared_ptr<T>weak_ptr<T>라는 타입의 등장으로 레퍼런스 카운팅 수준의 쓰레기 수집을 지원하게 되었다.

2. 동작 원리 기초

(가장 많이 알려진 자바 가상 머신 기준으로 서술)

단순하게 생각하면 쓰레기 수집기가 메모리 영역 사용을 감시하고 있다가 안전을 위해 강제로 프로그램을 모두 멈추고, 더 이상 쓰지 않는 메모리를 모두 추적해서 확보한 후 프로그램 실행을 다시 시작하면 될 것 같아 보인다.그러나 이렇게 하면 당연히 사용자는 뭔가 하다가 불규칙하게, 뜬금없이 프로그램이 뚝뚝 끊긴다고 느낄 수밖에 없다. 덕분에 자바는 등장한 초기에 성능이 영 아니라고 신나게 욕을 먹었다.

이후 똑똑한 사람들이 각종 프로그램의 메모리 사용 패턴을 관찰해보니, 객체에 메모리를 할당해서 더 안 쓰는 쓰레기가 될 때까지 걸리는 시간을 추적했을 때 대부분의 객체는 잠깐 쓰고 금세 버려지며, 반대로 오래 살아남아서 쓰이는 경우는 그렇게 많지 않다는 경향이 발견되었다.

따라서 위 경향에 맞추어 잠깐 쓰고 사라져도 되는 객체를 상대적으로 크기가 작은 New 영역에 할당하고, New 영역에서 기준 시간 이상으로 오래 살아남은 객체가 있다면 Old 영역으로 이동시켜 "세대" 구분을 하는 방법이 사용되고 있다. 대부분의 쓰레기는 New 영역에서 발생하므로, 상대적으로 작은 영역만 추적하면 적은 시간과 비용만 들여서 필요한 메모리를 짧은 시간 안에 확보할 수 있는 것이다.

하지만 이 "세대" 개념을 도입한 쓰레기 수집기에도 한계는 있다. 대부분의 메모리 할당 요청은 New 영역에서 처리되므로 금세 처리 가능하겠지만, 만약 메모리 사용량이 많아지다가 Old 영역까지 꽉 차게 차면 위에 적힌 것처럼 모든 메모리 영역을 전부 뒤져야 하는 건 피할 수 없고, 반응성이 저하되는 문제도 피할 수 없다. 대부분의 경우를 잘 처리하긴 하지만 만능은 아니란 것.
----
  • [1] 때문에 이런 언어들이 해 주는 메모리 관리란 그냥 전역변수 혹은 기껏해야 호출 스택에 의한 지역변수 관리 정도가 전부였다. 정 아쉬우면 BASIC의 PEEK/POKE 명령 같은 걸로 메모리 할당도 프로그래머가 직접 하던지 (......)
  • [2] 만드는 것도 어렵다. 외계인 고문하던 리즈시절 에릭슨에서 수년간 개발해서 겨우 상용화한것
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2015-03-28 22:06:28
Processing time 0.0725 sec