저수준 언어(C)
컴퓨터에 친화적인 형태로 정밀한 문법이 요구되며, 유지보수가 어렵지만 저사양 컴퓨터(프린터, 스캐너, IPTV 등) 에서도 빠르게 동작한다.
고수준 언어(Java, Javascript, Python, ...)
인간 친화적인 형태로 맥락에 따라 많은 부분이 생략 가능하고 유지보수가 쉽지만 고사양 컴퓨터에서만 동작한다.
모든 프로그래밍 언어에서는 동적으로 할당했던 메모리영역을 관리해야 한다. 따라서 메모리속 데이터는 생존 주기를 가지는 데 이를 메모리 생존 주기라고 부른다
메모리 생존 주기는 모든 프로그래밍 언어가 다 비슷하다.
2번 단계는 모든 언어에서 개발자가 명시해야 하지만 1, 3번 단계는 저사양 언어일 경우 개발자가 명시해야 하고, 고수준 언어에서는 암묵적으로 자동 실행 된다.
javascript는 값을 선언할 때 자동으로 메모리를 할당한다.
값을 메모리에 할당하기 위해 값을 지닐 변수를 초기화해서 메모리 공간을 확보하게 되는데 이것이 우리가 아는 초기화 단계이다.
let a; /// a === undefined
또는 함수 호출 결과를 변수에 할당할 때 메모리 할당이 일어난다.
let b = new Date() // Date 객체를 위해 메모리를 할당
변수나 객체 속성의 값을 읽고 쓰거나 함수에 인자를 전달해 호출하는 등을 수행하는 것을 의미한다.
저수준 언어에서는 메모리를 해제할 때 개발자가 직접 결정하고 해제한다.
하지만 javascript 같은 고사양 언어는 가비지 컬렉터가 자동 판단하고, 해제한다.
가비지 컬렉터가 자동으로 메모리를 관리하는 것을 의미하며, 다음과 같이 동작한다.
메모리 가 필요한지 아닌지를 결정짓는 것은 비 결정적인 문제(정확한 예측이 불가능)이기 때문에 가비지 컬렉터들은 다음 2가지 알고리즘을 통해 제한적인 해결책을 구현한다.
가비지 컬렉션 알고리즘은 참조를 기반으로 작동한다.
참조 세기 알고리즘은 어디서도 참조되지 않는 객체를 필요하지 않은 객체로 여긴다.
let js = {name: 'javascript'} // js는 ts로부터 참조되고 있기에 가비지 컬렉션 대상 X
let ja = {name: 'Java'} // 아무도 참조하고 있지 않기에 가비지 컬렉션 대상 O
let ts = js
참조 세기 알고리즘은 순환 참조에 취약하다는 문제점을 가지고 있다.
function J() {
let a:{};
let b:{};
a.c=b;
b.c=a;
}
J();
a와 b는 함수가 호출된 이후 스코프를 벗어나기 때문에 실질적으로 쓸모가 없어지지만 참조 세기 알고리즘은 두 객체가 서로 참조되고 있기 때문에 a와 b를 가비지 컬렉션의 대상으로 지정하지 않는다. 이는 불필요한 메모리가 낭비되고 있음을 의미하며 이를 메모리 누수라고 표현한다.
mark and sweep(편의상 MAS로 축약한다.)은 닿을 수 없는 객체를 필요하지 않은 객체로 여긴다.
MAS는 roots라는 객체의 집합(javascript에서는 전역변수들을 의미)을 가지는데 roots를 통한 접근 가능 여부를 통해 객체의 필요성을 검토한다.
MAS는 roots부터 접근 가능한 객체만을 필요한 객체로 보기 때문에 스코프를 벗어나는 순환참조를 걸러낼 수 있다.
참조되지 않는 객체는 닿을 수 없는 객체이지만 역은 성립하지 않기 때문에 MAS는 참조-세기 알고리즘 보다 효율적인 알고리즘이며, 대부분의 브라우저는 MAS방식의 가비지 콜렉션을 사용한다.
가비지 컬렉션을 지원하는 언어에서 메모리 누수는 주로 예상치 못한 참조에서 나타난다.
불필요한 참조가 이루어지고 있어 가비지 컬렉션이 해제시키지 못하고 있는 상태이며, 주로 다음과 같은 상황에서 나타난다.
javascript에서 명시적인 방법이나 프로그래밍적으로 가비지 컬렉션을 작동시킬 수 없고, 가비지 컬렉터가 메모리의 실사용 여부를 완벽하게 구분하는 것을 불가능 하기 때문에 코드를 작성할때 메모리 누수가 일어나지 않도록 유의하고, 메모리 누수가 일어난다면 해당 부분을 직접 처리해야 한다.
Reference
코드스테이츠
MDN-자습서-Javascript 메모리 관리