자바스크립트 메모리

머맨·2021년 9월 26일
0

자바스크립트는 객체가 생성되었을때 자동으로 메모리를 할당하고 쓸모 없어졌을 때 자동으로 해제한다. (가비지 컬렉션)

메모리 생존주기

  1. 필요할때 할당한다.
  2. 사용한다. (읽기, 쓰기)
  3. 필요없어지면 해제한다.

(자바스크립트와 같은 고수준 언어에서는 1,3 번의 경우 암묵적으로 작동한다)

메모리 할당

자바스크립트는 값을 선언할 때 자동으로 메모리를 할당

const n = 123; // 선언과 동시 메모리 할당

function f(a){
	return a + 2;
} // 함수를 위한 할당 ( 함수는 호출 가능한 오브젝트이다 )

메모리 사용

할당된 메모리를 읽고 쓰는 것을 의미, 변수나 객체 속성의 값을 일고 쓰거나 함수 호출시 함수에 인수를 전달하여 수행 하는것.

메모리 해제 (가비지 컬렉션)

자바스크립트는 더이상 메모리가 필요없을때 가비지 컬렉션이라는 자동 메모리 관리 방법을 사용되는데 그 원리

가비지 컬렉션

1.가비지 컬렉션의 알고리즘이 의존하고 있는 주요 개념은 참조(reference) 이다.
객체는 만약 그것을 가리키는 참조가 하나도 없는 경우 가비지 컬렉션 대상으로 간주

var x = {
  a: {
    b: 2,
  },
};
// 2개의 오브젝트가 생성되었다. 하나의 오브젝트는 다른 오브젝트의 속성으로 참조된다.
// 나머지 하나는 'x' 변수에 할당되었다.
// 명백하게 가비지 콜렉션 수행될 메모리는 하나도 없다.

var y = x; // 'y' 변수는 위의 오브젝트를 참조하는 두 번째 변수이다.
x = 1; // 이제 'y' 변수가 위의 오브젝트를 참조하는 유일한 변수가 되었다.
console.log(x);
console.log(y);

var z = y.a; // 위의 오브젝트의 'a' 속성을 참조했다.
// 이제 'y.a'는 두 개의 참조를 가진다.
// 'y'가 속성으로 참조하고 'z'라는 변수가 참조한다.

y = 'mozilla'; // 이제 맨 처음 'y' 변수가 참조했던 오브젝트를 참조하는 오브젝트는 없다.
// (역자: 참조하는 유일한 변수였던 y에 다른 값을 대입했다)
// 이제 오브젝트에 가비지 콜렉션이 수행될 수 있을까?
// 아니다. 오브젝트의 'a' 속성이 여전히 'z' 변수에 의해 참조되므로
// 메모리를 해제할 수 없다.

z = null; // 'z' 변수에 다른 값을 할당했다.
// 이제 맨 처음 'x' 변수가 참조했던 오브젝트를 참조하는
// 다른 변수는 없으므로 가비지 콜렉션이 수행된다.
  1. 순환 참조 문제

이렇게 참조에 의해 가비지 컬렉션을 수행하는데 있어서 한계가 있다. 두 객체가 서로 참조하는 속성으로 생성되어 순환 구조를 생성한다.

function f() {
  var x = {};
  var y = {};
  x.a = y;         // x는 y를 참조한다.
  y.a = x;         // y는 x를 참조한다.

  return "azerty";
}

f();

함수 호출이 완료되면 이 두 객체는 스코프를 벗어나며 그 시점에서 두 객체는 불필요해지므로 할당된 메모리는 회수 되어야하는데, 두객체가 서로를 참조하고 있으므로, 가비지 컬렉션 대상으로 표시하지 않게된다. 이러한 순환 참조는 메모리 누수의 흔한 원인이다.

3.이러한 문제들을 해결하기 위해 mark and sweep 알고리즘 을 사용하여 개선

이 알고리즘은 더이상 필요없는 오브젝트, 닿을 수 없는 오브젝트로 정의한다. 다음 세 단계를 거친다.

1) roots(전역)로서 완전한 목록을 만들고
2) 그 자식들을 검사해서 루트에서부터 닿을수 없는 것들을 가비지로 표시
3) 가비지 컬렉터 활성으로 표시되지 않은 모든 메모리를 os에 반환

자바스크립트의 흔한 메모리 누수 4가지

프로그램에서 사용을 했다가 더 이상 필요하지 않지만 아직 os 나 자유메모리 풀에 반환되지 않은 메모리 조각들을 말함

1.전역 변수

function foo(arg) {
    bar = "some text";
}
function foo() {
    this.var1 = "potential accidental global";
}

이와 같이 전역변수는 가비지 컬렉터가 정리할 수 없다. 그러므로 임시로 정보를 저장하거나 많은 양의 정보를 처리 할 때 사용하는 전역 변수는 특별히 세심한 주의를 기울일필요가 있습니다. 꼭 그래야 한다면 전역 변수를 사용할 수도 있지만 사용을 마친 다음에는 꼭 null로 할당하거나 다른 변수로 할당 해야한다.

이러한 문제점들을 회피하기 위해 자바스크립트 파일 상단에 use strict를 사용하면 예상치 못한 전역 변수 생성을 방지할 수 있는 파싱을 할 수 있다.

2.잊혀진 타이머 혹은 콜백함수

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

node 가 삭제될 가능성이 있다. 즉 더 이상 필요하지 않은 node 나 데이터를 참조하는 타이머를 인터벌로 여전히 활성화 되어 있기 떄문에 메모리 해제를 할 수 없었으나 현대의 가비지콜렉터로 참조하지않는 객체는 자동으로 메모리를 해제한다.

3.DOM 외부에서의 참조

var elements = {
    td: document.getElementById('td') // <table> 내 셀 태그인 <td>
};

function removeTable() {
    document.body.removeChild(document.getElementById('tableID')); // <table> 태그 제거
    // td 엘리먼트가 아직 참조 중이므로, 모든 table 내 데이터가 그대로 유지
}

이처럼 DOM 엘리먼트 또한 객체로서 참조하고 있기 때문에 해당 td뿐만 아니라 해당 테이블을 지웠음에도 메모리에서 지워 지지 않고 남아있게 되어 누수 발생

4.클로저

var theThing = null

var replaceThing = function () {
  var originalThing = theThing
  // 상위 스코프인 originalThing을 참조하는 스코프를 갖게됨
  // 동시에 theThing 도 참조하게됨.
  var unused = function () {
    if (originalThing) console.log('hi')
  }

  //
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage)
    },
  }
}
setInterval(replaceThing, 1000)

replaceThing이 호출될 때마다, 큰 사이즈의 배열 longStr과 someMethod 클로저를 생성한다. 동시에 unused 변수는 originalThing을 참조하는 클로저를 가지게 된다. 중요한 것은, unused와 같은 내부함수에서는 자신을 둘러싼 부모함수의 스코프를 공유한다는 것이다. (스코프 체이닝) unused내부 함수가 없다면, replaceThing은 매번 실행 될 때 마다 길이가 큰 문자열을 생성하긴 하겠지만, 최신 자바스크립트 엔진 (v8과 같은) 에서는 이전에 호출된 originalThing이 사용 되지 않았음을 파악하고, 이전 값을 해제하여 메모리 사용량을 유지 시킨다. 하지만 위 코드에서는 unused의 내부 함수로 인해 계속해서 originalThing을 참조하게 되고 unused가 사용되지 않더라도, 이 코드가 실행 될 때마다 메모리 사용량이 꾸준히 증가하는 것을 볼 수 있다. 따라서 GC가 작동하더라도 메모리 사용량은 크게 줄어들지 않게 된다. 본질적으로 클로저의 참조 목록이 생성되면 (theThing으로 부터 생겨난 root), 이 클로저 내부에는 큰사이즈 배열에 대한 간접적인 참쪼도 동반하게 되므로 메모리 누수가 발생된다.

profile
코맨코맨

0개의 댓글