2/ 메모리 관리와 가비지 콜렉션

2/ 메모리 관리와 가비지 콜렉션

목차

프론트엔드 개발자를 위한 자바스크립트 프로그래밍 책을 참고하여 정리합니다. 오류가 있다면 언제든지 댓글 남겨주세요.

요약
가비지 콜렉션은 더 이상 사용하지 않는 메모리를 회수하는 역할을 한다. 메모리를 회수하기 전 사용하지 않는 변수를 체킹해야 하는데 이때 표시하고 지우기 방법과 참조 카운팅이 있다. 표시하고 지우기 방법은 처음 컨텍스트의 모든 변수에 마킹을 한 후, 값이 할당되어있거나 참조되어있는 변수의 마킹을 지운다. 지웠음에도 마킹이 남아있는 변수를 회수하는 방식이다. 참조 카운팅은 참조 카운트가 0인 변수를 회수하는 방식인데 순환참조의 카운팅 한계가 있기 때문에 주로 표시하고 지우기 방법을 사용한다. 가비지 콜렉터 사이클이 도는 것은 상당한 비용이 발생한다. 메모리 누수 관리를 위해서는 사용하지 않는 변수나 객체는 모두 null로 재할당하여 가비지 컬렉터가 다음 사이클 때 회수하도록 하는 방법, 크롬 개발자도구의 퍼포먼스 탭에서 확인하는 방법 등이 있다.


1. 가비지 콜렉션

자바스크립트는 인터프리터 언어이다.
(인터프리터 언어란 목적 파일 산출과정이 없이 실행과 동시에 줄 단위로 번역이 된고, 저용량 소스에 적합하다. 코드가 실행되는 시점이 런타임이다. 인터프리터와 컴파일러의 차이점)

고급 언어 인터프리터는 가비지 콜렉터 라는 소프트웨어를 가지고 있다.
가비지 컬렉터(Garbage Collector)란 메모리 할당을 추적하고 할당된 메모리가 더 이상 필요 없어졌을 때 해체하는 작업이다.

이 프로세스는 주기적으로 실행되는데 코드 실행 중에 특정 시점에서 메모리를 회수하도록 지정할 수도 있다. C나 C++같은 언어에서는 메모리 추적이 매우 중요하기 때문에 메모리 관리가 힘들지만, 자바스크립트는 필요한 메모리를 자동으로 할당하고 더 이상 사용하지 않는 메모리는 자동으로 회수하므로 개발자가 직접 메모리를 관리하지 않아도 된다.

가비지 컬렉터는 항상 필요없어진 메모리만을 해제하지만, 모든 필요없어진 메모리를 해제하는 건 아니다. 즉, ‘더 이상 필요없는 모든 메모리’가 아니라 ‘더 이상 필요 없는 몇몇 메모리’를 찾아낸다. 이는 가비지 콜렉션 알고리즘의 한계점이 있기 때문이다.


2. GC가 회수해야할 메모리 식별은 어떻게 하나요?

무튼, 어떤 변수가 더 이상 사용되지 않는지, 사용될 가능성이 있는 변수는 무엇인지 추적해야 메모리 회수 대상을 정할 수 있다. 식별 기준은 2가지이다.

  1. 표시하고 지우기 Mark and Sweep
  2. 참조 카운팅 Reference counting

2.1 표시하고 지우기 Mark and Sweep

가장 널리 쓰이는 컬렉션 방법이다.
변수가 특정 컨텍스트 안에서 사용할 것으로 정의되면 그 변수는 그 컨텍스트 안에 있는 것으로 표시된다. 표시한는 구체적인 방법은 알 필요는 없다..

GC가 작동하면

  1. 메모리에 저장된 변수 전체를 표시한다.
  2. 컨텍스트에 있는 변수, 컨텍스트에 있는 변수가 참조하는 변수의 표시를 지운다.
  3. 표시가 지워지지 않는 변수를 삭제한다.

가비지 컬렉터는 ‘메모리 청소’를 실행해 표시가 남아 있는 값을 모두 파괴하고 메모리를 회수한다.

2.2 참조 카운팅 Reference counting

각 값이 얼마나 많이 참조 되었는지 추적한다.

  1. 변수를 선언하고 참조 값이 할당되면 참조 카운트는 1이다.
  2. 다른 변수가 같은 값을 참조하면 참조 카운트가 늘어난다.
  3. 마찬가지로 해당 값을 참조하는 변수에 다른 값을 할당하면 원래 값의 참조 카운트가 줄어든다.
  4. 값의 참조 카운트가 0이 되면 해당 값에 접근할 방법이 없으며, 메모리를 회수해도 안전하다.
  5. 다음 가비지 컬렉터를 실행할 때 참조 카운트가 0인 값에서 사용하던 메모리를 회수한다.

순환 참조 문제
이 알고리즘은 두 object가 서로를 참조하면 문제가 발생한다.

1
2
3
4
5
6
7
function problem(){
let objectA = new Object(); // objectA : reference count 1
let objectB = new Object(); // objectB : reference count 2

objectA.someOtherObject = objectB; // objectA : 2
objectB.anotherObject = objectA; // objectB : 2
}

위의 코드를 보면 새로운 객체가 생성되면서 참조 카운트가 1이되고,
서로 참조하게 되면서 참조카운트가 2가 된다.
이 상태에서 스코프를 벗어나게 되면, 해당 변수는 사용되지 않는데, 벗어났음에도 카운트가 0이 아니기 때문에 GC가 컬렉션을 하지 않게 된다. 이는 곧 메모리 낭비로 이어진다.
이때는 강제로 null을 항당해서 참조했던 값으로의 연결을 끊어줘야한다.

그래서 대부분의 브라우저에서는 표시하고 지우기 방법을 쓴다.


3. 가비지 콜렉터와 성능문제

GC는 주기적으로 실행되며 메모리 내에 할당된 변수가 많다면 상당한 비용이 드는 작업이므로 GC를 실행하는 타이밍이 중요하다. 익스플로러는 가비지 컬렉터를 너무 자주 실행하여 성능 문제를 일으키는 것으로 악명이 놓다.


4. 자바스크립트로 프로그래밍시 내가 할 수 있는 메모리 관리

웹 브라우저에서 사용할 수 있는 메모리는 일반적인 데스크톱 애플리케이션의 가용 메모리에 비해 매우 적다. 적은 메모리만 할당받는 주된 이유는 웹 페이지에서 실행하는 자바스크립트가 시스템 메모리를 전부 사용해서 운영체제를 다운시키는 일을 방지하기 위함이다.
메모리 제한은 변수 할당 뿐만 아니라 호출스택, 스레드에서 실행할 수 있는 문장수에도 영향을 미친다.
즉! 가능한 최소한의 메모리만 사용해야 페이지의 성능을 올릴 수 있다.

필요 없어진 데이터에는 null을 할당하여 참조를 제거(dereference)하는 편이 좋다. 이론상은 그렇지만, 실제로는 많이 사용하지 않는다. 참조를 하고 있는지에 대해서 개발자가 판단하기가 어렵기 때문에 오히려 전체 흐름을 망가뜨리는 행위가 될 수 있다. 우리도 모르게 어디선가 side effect가 발생할 수도있고, 복잡성이 증가한다.(면접 때 면접관님께서 정정해주셨다!)
수동으로 참조 제거해야 할 대상은 주로 전역변수전역 객체의 프로퍼티이다.
(지역변수는 컨텍스트를 빠져나가는 순간 자동으로 참조가 제거된다.)

참조 제거의 요점은 값의 컨텍스트를 없애서 다음에 가비지콜렉션을 실행할 때 해당 메모리를 회수하도록 하는 것이다.

  • 사용하지 않은 객체, 변수는 모두 null 로 초기화

  • 이벤트 핸들러를 바인딩 했다면, 모두 언바인딩

  • DOM 을 동적으로 생성했다면, 불필요한 객체, 속성(값)을 DOM 에 삽입하지 말자.

  • 크롬 개발자도구의 Performace탭에서 timeline확인해보자.


(번외 (참고 글)) V8_Efficient Garbage Collection

V8은 객체가 사용하다가 더 이상 필요 없게 된 메모리를 가비지 콜렉션(garbage collection)이라고 알려진 작업을 통해서 다시 찾아온다. 빠른 객체 할당을 보장하고, 가비지 콜렉션으로 인한 프로그램 정지 시간을 단축시키며, 메모리 파편화를 제거하기 위해서 stop-the-world 방식의 세대적이고, 정확한 가비지 콜렉터(garbage collector)를 채택하고 있다.

이것은 다음을 의미한다.

  • 가비지 콜렉션 사이클을 수행할 때 프로그램 실행을 멈춘다.
  • 가비지 콜렉션 사이클 중 대부분의 시간을 객체의 힙(heap)의 영역을 처리하는데 사용함으로써 어플리케이션 멈춤 현상을 최소화 한다.
  • 모든 객체가 포인터가 메모리의 어디에 있는지 항상 정확히 알고 있다. 이를 통해 객체를 포인터로 잘못 식별하여 메모리 누수를 일으키는 문제를 피할 수 있다.

V8에서 객체의 heap은 2개의 부분으로 나누어져 있다. 새로 객체가 생성되는 영역과 가비지 콜렉션 사이클이 진행되는 동안에 살아남은 객체가 있는 영역. 객체가 가비지 콜렉션으로 들어가면 V8은 객체의 포인터를 갱신한다.

변수를 생성하는 것 자체가 메모리를 사용하는 것인데, 이를 회수하고 관리하는 가비지컬렉션이 있다는 것에 흥미로웠다.
메모리 누수를 막아서 최소한의 메모리 사용으로 페이지 성능을 향상하는 것이 목적이지만, 사이클이 너무 많이 실행되면 이것 또한 자원낭비.
변수의 할당의 최소화하고, 코드 재사용성을 높이고, 전역변수나 전역객체의 프로퍼티가 존재한다면 마지막에 null을 할당해보자.


참고링크

  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management
  2. http://huns.me/development/452
  3. https://v8project.blogspot.kr/2015/08/getting-garbage-collection-for-free.html
  4. http://hapina.tistory.com/112
📚