1년 이상 React 개발을 하면서 점점 프레임워크에 국한되는 스킬과 기본 지식만을 쌓고 있다는 것을 알게 되었습니다. 개발을 배우던 초기에 Javascript 관련 습득했던 지식들이 점점 잊혀졌습니다. 실행 순서들은 console로 찍으면서 파악하고, Javascript에 맞는 구조보다는 프레임워크에서 요구하는 스펙으로만 개발을 했습니다.
그래서 내가 지금 하고 있는 개발에 조금 더 본질적으로 다가가기 위해 Javascript 개념과 원리를 찾는 방향으로 공부해보기로 했습니다. 그 첫걸음이 "코어 자바스크립트"란 책이고 그 리뷰를 블로그에 기록하려고 합니다.
단순히 책 내용을 정리하는 것은 의미가 없을 것 같고, 책을 읽으면서 궁금했던 점을 스스로 찾아보고 정리하는 과정이 될 것 같습니다.
Javascript는 형변환이 자유롭습니다. 다시 말하면, 데이터가 메모리를 차지하는 영역이 가변적이라고 말할 수 있습니다. 형변환의 자유로움이 가능하도록 만드는 데이터 할당의 특징을 도식으로 나타내면 다음과 같습니다.
var a = 'abc';
var b = 'aaa';
위의 변수 할당이 변수명 "a"에 "abc"라는 string data를 할당하였습니다. javascript에서는 데이터 영역에 할당하려는 데이터를 입력하고 그 주소값을 변수 영역에서 변수명 "a" 할당하게 됩니다. 같은 값을 가지는 데이터가 데이터 영역에 없다면, 무조건 새로운 데이터를 만들게 됩니다.
데이터 영역을 따로 만들어서 변수에 주소값을 할당하는 이유는 가변적인 데이터를 할당하기 위해서입니다. 만약 작은 데이터가 할당되어 있다가 큰 데이터로 재할당했을 때, 변수 영역에 데이터를 직접 할당하게 되면 기존에 잡힌 메모리 공간을 넘어서는 데이터를 할당해야 하는 상황이 벌어질 수 있습니다. 그렇게 된다면 뒤에 입력된 데이터들의 자리가 계속 밀리겠죠. 그렇게 되면 메모리의 공간을 비효율적으로 사용하게 됩니다.
반면, 데이터 영역을 따로 만들 경우 데이터 크기가 다른 데이터를 할당했을 경우 데이터 영역에 뒤에 붙여서 데이터를 저장하고 주소값만 바꾸어 주면 되기 때문에 상대적으로 효율적으로 메모리를 이용할 수 있습니다.
a = 'aaa';
변수 a
에 b
와 같은 데이터를 재할당 하였습니다. 이 때는 같은 데이터가 이미 데이터 영역에 들어있으므로 해당 데이터 주소값을 할당받게 됩니다. 여기서 중요한 것은 a
를 재할당 하였다고 @5003
에 있는 데이터를 변경하지 않았다는 것입니다. 데이터 영역의 데이터는 변경하여 저장하지 않고 새롭게 저장하고 기본의 데이터는 가비지 컬렉터가 수거합니다.
데이터 영역의 데이터들 중에 자신의 주소값을 사용하는 변수가 없다면 Garbage Collector의 수거 대상이 됩니다.
가비지컬렉션(garbage collection)은 오브젝트(메모리 입장에서는 함수 스코프도 포함)가 더 이상 필요하지 않을 때, 해당 오브젝트를 메모리에서 제거하는 동작을 의미합니다.
자바스크립트에서는 명시적으로 메모리를 해제하는 명령어를 가지고 있지 않습니다. 따라서 가비지 컬렉션 로직을 이해하고 있어야 메모리 누수에 적극적으로 대처할 수 있습니다.
가비지컬렉터가 "가비지"를 인식하는 방법
기본적으로 "더 이상 사용되지 않은 오브젝트"인지를 확인하기 위해 참조라는 개념을 사용합니다.
A라는 메모리를 통해 (명시적이든 암시적이든) B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다" 라고 한다.
출처: mdn 자바스크립트의 메모리 관리
참조-세기(Reference-counting) 가비지 콜렉션
"더 이상 필요없는 오브젝트"를 "어떤 다른 오브젝트도 참조하지 않는 오브젝트"라고 정의합니다. 이를 참조하는 다른 오브젝트가 하나도 없는 경우, 수집이 가능합니다. 이 방법에는 한계점이 있는데, 순환 참조의 경우 가비지로 인식하지 못한다는 단점이 있습니다. 다음의 예로 설명하겠습니다.
// 출처: mdn 자바스크립트의 메모리 관리
function f(){
var o = {};
var o2 = {};
o.a = o2; // o는 o2를 참조한다.
o2.a = o; // o2는 o를 참조한다.
return "azerty";
}
f();
위의 예에서 o
와 o2
오브젝트는 각각 서로를 참조하고 있으나 외부 오브젝트와는 연결되어 있지 않습니다. 따라서 메모리의 입장에서는 두 개의 오브젝트 모두를 가비지로 인식해야 합니다. 하지만 참조-세기 가비지 콜렉션에서는 서로를 참조하고 있으므로 해당 o
와 o2
오브젝트를 가비지로 인식하지 않습니다.
표시하고-쓸기(Mark-and-sweep) 알고리즘
"더 이상 필요없는 오브젝트"를 "닿을 수 없는 오브젝트"로 정의하고 있습니다.
전역변수에서 출발하여 닿을 수 없는 오브젝트들을 "가비지"로 인식하여 sweep하는 구조입니다. 아래의 이미지에서 간단히 알아볼 수 있습니다.
이런 방식으로 참조-세기 가비지 콜렉션에서 한계점으로 지적했던 순환 참조 또한 예외없이 가비지로 인식할 수 있게 되었습니다. 2012년부터 최신 브라우저들은 Mark-and-sweep 알고리즘으로 가비지 콜렉션을 사용하고 있습니다.
코어 자바스크립트를 읽으면서 새롭게 알았던 것은 불변성(immutability)에 대한 개념입니다. 흔히 Javascript를 처음 배울 때, 데이터 타입에는 기본형과 참조형이 있고 기본형은 불변성을 가지고 참조형은 가변성을 가진다고 배웁니다. 그런데 위에 설명했던 것처럼 기본형 또한 데이터 영역을 활용하여 변수할당을 하고 그 주소값만 가지고 있다면 이 또한 참조형 아닐까? 라는 의문이 들었습니다.
조금 더 자세히 알아보기 위해 참조형 데이터의 할당 과정을 알아봐야겠습니다.
var obj1 = {
a: 1,
b: 'aaa',
};
참조형 데이터인 객체를 할당하는 과정입니다.
obj1
이라는 이름으로 기본형 데이터 할당과 같이 @5002 데이터에 객체 데이터를 할당하고 주소값을 할당합니다.a
와 b
이름으로 데이터가 또 할당되어 있으므로 데이터 영역에 해당 데이터를 할당하고 주소값을 할당합니다.여기서 obj1.a
를 다른 데이터로 재할당해보겠습니다.
obj1.a = 'aaa';
obj1.a
에 obj1.b
와 같은 데이터를 재할당하였습니다. 이때 a
는 기본형 데이터 할당과 동일하게 데이터 영역에서 할당할 데이터가 있는지 찾아보고 해당 주소값을 할당합니다. 하지만 변하지 않은 것이 있습니다. 바로 obj1
에 할당된 데이터 주소값입니다. obj1
의 데이터는 그대로이고 그 안의 데이터는 새롭게 변경되었습니다.
결과적으로 obj1
은 그 속의 데이터가 변경된 것처럼 보입니다. 데이터 할당에 의해 "가변"적으로 변경된 것처럼 보입니다. 흔히 데이터의 가변성/불변성을 구분하는 기준이 데이터 영역이 되는 것입니다.
데이터 타입에서 기본형/불변성을 구분하는 기준은 데이터 영역의 메모리입니다.
기본형 데이터는 재할당할 때 새로운 데이터를 데이터 영역에 할당하고 변수영역에서 해당 주소값을 할당합니다. 따라서 기존에 할당했던 데이터 영역의 메모리는 변하지 않았습니다. 계속해서 새로운 데이터를 데이터 영역에 저장하게 됩니다.
반면 참조형 데이터는 객체 내부의 데이터를 재할당한다고 해서 새로운 객체를 데이터 영역에 할당하지 않습니다. 주소값은 그대로 유지하고 객체 내부의 데이터의 주소값을 갱신합니다. 그렇게 되면 데이터 영역의 객체 데이터는 변경될 수 있습니다.
개발 공부 초기에 당연하다는 생각을 하고 넘어간 것들에 대해 다시 생각해봐야겠다는 다짐이 들게 되었습니다. 데이터 타입은 가장 먼저 배우는데 아무런 의심없이 그렇구나 하고 넘어갔었습니다.
또한 가비지 컬렉션도 당연히 자동으로 메모리를 최적화 해준다고만 생각하고 메모리 관리에 대해서 고려하지 않은 채 실무에 임하고 있었다는 생각이 들어 부끄러웠습니다.
단순히 변수 할당을 하더라고 생각을 넓게 할 수 있는 계기가 되었고, 이 책을 읽으면서 열심히 복습해봐야겠다는 생각이 들었습니다. 그리고 이 글 또한 생각을 정리하다가 정리 안 된 머리 속을 그대로 반영한 것 같아서 반성합니다ㅠㅠ 나중에 다시 보면 더 정리된 생각으로 리팩토링 할 수 있을 것이라고 생각되어 이대로 퍼블리싱하려고 합니다.
참고자료
https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec
코어자바스크립트 위키북스 정재남 지음