[CS] 가비지 컬렉션

hzn·2022년 11월 30일
0

etc

목록 보기
6/13
post-thumbnail

자바스크립트의 가비지 컬렉션

  • 프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것
  • 자바스크립트고수준 언어(인간 친화적 언어)로서, 객체가 생성되었을 때 자동으로 메모리를 할당하고 필요하지 않다면 자동으로 해제하는 가비지 컬렉션이 내장되어 있다.
  • 그 외 자바, C# 등이 이 기능을 가지고 있다.

cf) 저수준 언어의 경우

  • C 언어 같은 저수준 언어(기계 친화적 언어)에서는 메모리 관리를 위해 malloc()free()를 사용해 개발자가 스스로 메모리를 할당하고 해제해야 한다.

메모리 생존 주기

  • 프로그래밍 언어에 관계 없이 비슷
  1. 필요할 때 개발자가 할당.
  2. 할당된 메모리를 사용. (Read and Write)
  3. 메모리가 더이상 필요하지 않으면 해제.

👉🏽 2번은 모든 언어에서 명시적
👉🏽 1, 3번은 저수준 언어에서는 명시적, 자바스크립트 등 고수준 언어에서는 암묵적으로 작동.

1. 메모리 할당

  • JavaScript는 프로그래머 대신, 값을 선언할 때 자동으로 메모리를 할당해준다.
  • 정수, 문자열, 함수, 객체 모든 부분에서 자동적으로 일어난다.
let arr = [100, 200, 300, 400]
  • JavaScript가 배열과 배열에 담긴 값들을 위한 메모리 크기 할당을 알아서 진행.

2. 할당된 메모리 사용 (값 사용)

  • 기본적으로 할당된 메모리를 읽고 쓰는 것을 의미
  • 변수나 객체 속성의 값을 읽고 쓰거나, 함수 호출 시에 함수에 인수를 전달하여 수행하는 방식으로 일어난다.

3. 메모리 해제

  • 할당된 메모리가 더이상 필요 없다면 해제를 해야 앱의 성능을 저하시키지 않는다.
  • 가비지 컬렉션은 메모리 할당을 추적하고, 할당된 메모리 블록이 더이상 필요하지 않게 되었는지를 “스스로” 판단하여 해당 메모리를 해제한다. 그러나 언어(자바스크립트) 스스로 메모리가 여전히 필요한지 필요하지 않은지 판단하는 것은 비결정적인 영역이다.
  • 비결정적 영역 : 100% 확실하지 않으나 결정을 내려야 하는 영역
  • 그래서 고수준 언어에 내장된 가비지 컬렉터들은 제한적인 해결책을 구현한다.

대표적인 가비지 컬렉션 방법

  • 가비지 컬렉션 알고리즘 중 가장 유명한 것은 레퍼런스 카운팅트레이싱이다.
  • 이 두 알고리즘은 '참조' 개념에 의존한다.

참조(reference)

  • 명시적이든, 암묵적이든 관계없이 메모리 관리 관점에서 어떤 객체가 다른 객체에 접근할 수 있다면 다른 객체를 참조한다고 말한다.
  • ex) JavaScript 객체는 자신의 프로토타입(prototype)에 대해 암묵적인 참조를 갖고 있고, 자신의 속성(property) 값에 대한 명시적 참조도 가지고 있다.
  • '객체를 참조한다'고 말할 때, 객체는 일반적으로 자바스크립트 객체를 의미하지만 넓게는 함수 스코프(function scope)나 글로벌 렉시컬 스코프(global lexical scope)까지도 포함한다.
  • 렉시컬 스코핑(lexical scoping) : 변수 이름이 중첩된 함수에서 해석되는 방식을 정의하는 것으로, 중첩되어 있는 더 안쪽의 함수는 부모 함수가 값을 반환한 다음에도 부모 함수의 스코프를 포함하고 있다.

레퍼런스 카운팅(참조 횟수 계산)

  • 한 객체를 참조하는 변수의 수를 추적하는 방법
  • 가장 단순한 형태의 가비지 컬렉션 알고리즘.
  • 순환 참조로 인한 문제가 생길 가능성이 높다.

레퍼런스 카운팅 순서

1) 객체를 참조하는 변수는 처음에는 특정 메모리에 대해 레퍼런스가 하나뿐이지만, 변수의 레퍼런스가 복사될 때마다 레퍼런스 카운트가 늘어난다.
2) 객체를 참조하고 있던 변수의 값이 바뀌거나, 변수 스코프를 벗어나면 레퍼런스 카운트는 줄어든다.
3) 레퍼런스 카운트가 0이 되면, 그 객체와 관련한 메모리는 비울 수 있다.

레퍼런스 카운트가 0이 된다는 말은 아무도 그 객체에 대한 레퍼런스를 가지고 있지 않다는 말과 같다.

순환참조 예시

function reference() {
  var obj1 = {};
  var obj2 = {};
  obj1.p = obj2; 
  obj2.p = obj1; 
}
reference();
  • 두 객체가 생성되고 서로를 참조하고 있는 형태이기 때문에 순환 참조가 발생
  • 이 객체들은 함수 호출 뒤에는 스코프를 벗어나게 되므로 실질적으로 쓸모가 없게 된다.
  • 그래서 이들이 차지하던 메모리는 반환될 수 있지만, 레퍼런스 카운팅 알고리즘에서는 두 객체가 적어도 한 번은 참조한 것으로 간주되기 때문에 둘 다 가비지컬렉션이 될 수 없게 된다.

트레이싱

  • 한 객체에 flag를 두고, 가비지 컬렉션 사이클마다 flag에 표시 후 삭제하는 mark and sweep 방법
  • 현재 대부분의 가비지 컬렉션이 mark and sweep 알고리즘을 이용한 가비지 컬렉터를 장착하고 있다.
  • ‘참조받지 않는 객체’는 ‘닿을 수 없는 객체’기 때문에 가비지 컬렉션을 통해 메모리를 해제할 수 있기 때문에 레퍼런스 카운팅 방법보다 낫다.

트레이싱 순서

1) 객체에 in-use flag를 두고, 사이클마다 메모리 관리자가 모든 객체를 추적해서 사용 중인지 아닌지를 표시(mark).
2) 그 후 표시되지 않은 객체를 삭제(sweep)하는 단계를 통해 메모리를 해제한다.

(세분화된 순서)

0) mark and sweep 알고리즘이 객체가 필요한지 결정하기 위해 해당 객체에 닿을 수 있는지(reachable)를 판단한다.
1) 가비지컬렉터는 모든 루트의 완전한 목록을 만들어낸다.
2) 그 다음 모든 루트와 그 자식들을 검사해서 활성화 여부를 표시한다
(활성 상태이면 가비지가 아님 / 루트가 닿을 수 없는 것들은 가비지로 표시)
3) 마지막으로 가비지컬렉터는 활성으로 표시되지 않은 모든 메모리를 OS에 반환.

** 루트(Roots): 일반적으로 루트는 코드에서 참조되는 전역 변수. 예를 들어 자바스크립트에서 루트로 동작할 수 있는 전역 변수는 window 객체. Node.js에서 이와 동일한 객체는 global.

메모리 누수

  • Garbage collected 언어에서 메모리 누수의 주요 원인은 예상치 못한 참조.

예상치 못한 참조

  • 개발자는 더 이상 사용되지 않을 것이라 생각했지만, 어떠한 이유로 활성화 상태인 루트 트리 안에 존재하는 메모리 조각들.
  • 자바스크립트에서 예상치 못한 참조는 더이상 사용되지 않지만 코드 상 어딘가에 유지되어 해제되지 못한 변수들 (거의 대부분 개발자의 실수...)
  • 자바스크립트에서 발생할 수 있는 일반적인 메모리 누수 형태들을 이해하기 위해서는 흔히 까먹기 쉬운 참조들을 먼저 알 필요가 있다.

메모리 누수의 일반적인 형태

  • 우발적으로 생성된 전역변수
  • DOM 외부에서의 참조
  • 클로저의 잘못된 사용
  • 프로그래밍 언어의 메모리 관리 시스템이 특정 메모리가 실제 사용중인지 미사용중인지 완벽히 구분해내는 것은 사실상 불가능에 가깝다.
  • 오직 그 코드를 작성한 개발자들만이 해당 메모리 조각을 운영체제로 반환시킬 수 있는지 여부를 명확히 알 수 있기 때문에 해당 부분들을 잘 확인하여 메모리 누수가 일어나는 부분을 막을 줄 알아야 한다.

0개의 댓글