티스토리 뷰
요리 🥘
✏️ 요리사가 요리를 하고 있다. 요리를 하면서 조리대에 요리 재료들과 식기들을 놓고 진행 중이다. 계속해서 재료들과 식기들이 추가되다 보면 얹을 공간이 없어질 것이다. 요리사는 더 이상 요리가 진행되기 힘들다고 판단을 한다. 그러자 옆에 있는 조수가 사용하지 않거나 사용이 완료된 재료들을 치우면서 공간을 넓혀주는 장면을 기억해보자.
메모리 💾
✏️ 메모리를 쉽게 이해하기 위해서 요리하는 과정을 앞서 설명하였다. 컴퓨터 또한 요리하는 과정과 마찬가지로 처리할 작업들을 올려놓을 공간이 필요하다. 요리에 필요한 재료들과 식기 구들을 놓는 조리대를 메모리라고 생각해 보자. 조리대 공간이 넓으면 넓을수록 더 많은 요리가 동시에 가능할 것이다. 이는 우리가 컴퓨터를 구매할 때 램의 GB를 체크하는 이유이다.
사전적 정의 📕
📖 메모리란 0과 1로 표현된 값과 그 값이 저장되어 있는 주소를 의미한다.
Memory details 💾
✏️ 메모리는 Allocate memory, Use memory, Release memory 총 3단계의 생명주기를 가지고 있다. 할당 메모리, 사용 메모리, 방출(release) 메모리 순으로 쉽게 이해하도록 하자.
✏️ 코드 짜는 것을 레시피에 비유해보자. 코드에 작성된 변수, 함수, 객체들이 메모리 공간을 차지하는 요소들이다. 즉 조리대에 있는 요리 재료들과 식기들이라고 생각하면 된다. 하지만 요리를 하다가 조리대가 꽉 차면 더 이상 진행이 힘들다. 중간에 공간을 만들어주는 것이 필수적일 것이다. 앞서 언급하였듯이 중간에 공간을 만들어준 조수가 가비지 콜렉터(garbage collector)이다.
C나 C++과 같은 언어는 레시피(요리 과정)를 하나하나 다 작성해줘야 했다.
- 프로그래밍적으로 가비지 콜렉터에서 가비지(garbage)는 무엇을 의미하나요?
- 가비지의 사전적 의미 쓰레기와 마찬가지로 사용되지 않는 메모리지만, 아직 방출(release)되지 않은 메모리를 의미한다.
✏️ 아무리 개발자가 신중하게 코드를 작성한다 하여도 비워야하는 메모리 공간을 놓치는 일이 흔히 일어날 수 있다. 이것을 메모리 누수 Memory leak 이라고 한다.
이렇게 세어서 흘러내린 것들이 쌓여서 메모리를 꽉 채우면 컴퓨터가 뻗어버리는 것이다.
✏️ 그래서 자바스크립트와 같은 새로운 언어들에 가비지 콜렉터(garbage collector)를 도입하게 된 것이다. 앞서 언급하였던 조수를 생각해보자.
이렇게 알아서 메모리를 관리해주는 언어를 managed language 또는 garbage collected languages 라고 하며 반대는 unmanaged language 라고 한다.
메모리 관리가 그렇게 중요해?
✏️ 메모리 관리가 잘 되지 않으면 속도 저하, 예기치 못한 종료, 느린 응답속도와 같은 치명적인 문제점을 야기할 수 있다.
혹자는 자바스크립트는 가비지 콜렉팅 언어인데 메모리 관리를 내가 해야 해?라고 말할 수 있다. 하지만 가비지 콜렉터는 사람의 판단력을 갖춘 것이 아니기 때문에 지워야 할 것들을 100% 잡아내진 못한다. 따라서 좋은 개발자가 되기 위해서는 메모리 관리는 필수적인 것이다.
✏️ 메모리 누수를 방지하는 방식은 어느정도 공통점이 존재하지만 언어마다 무엇을 프로그래밍 하느냐에 따라 다른 부분들이 존재한다. 언어는 환경마다 특성이 다르기 때문에 A언어에서는 누수 방지에 효과적인 방식이 B언어에서는 성능 저하를 유발할 수 있는 것이다.
어떤 메모리가 필요없는 메모리 일까? (가비지 알고리즘)
1. Memory Reference
✏️ 메모리 참조 방식은 메모리가 참조 되어져 있느냐?를 기준으로 해당 메모리가 가비지(garbage)인지 아닌지 판별하는 방식이다. (과거 방식)
가비지 콜렉션은 한 변수가 어떤 변수에게 몇번이나 참조가 되었는지 카운팅(counting)을 통하여 그 수가 0이 되면 가비지(garbage)로 처리한다.
다음 코드를 보면서 더 쉽게 이해하자!
var o1 = {
o2 : {
x:1
}
};
var o3 = o1;
o1 = 1;
var o4 = o3.o2;
o3 = '374'
o4 = null;
1. o1의 변수에 o2라는 프로퍼티를 할당하고 o2안에 x:1 이라는 프로퍼티를 할당하였다.
2. o3에 o1을 할당하였다. → o3도 o1의 오브젝트(object)를 참조하고 있다.
3. o1에 1을 할당했다. → o1은 더 이상 객체의 형태가 아니다.
4. o4에 o3의 o2 프로퍼티 값을 할당하였다.
5. o3에 string을 할당하였다. → o4를 제외하고 최초의 o1 오브젝트를 참조하는 변수는 없다.
6. o4가 최초의 o1 오브젝트를 참조하고 있기 때문에 o1 오브젝트를 방출(release)할 수 없다.
7. o4에 null을 할당하였다. → 최초의 o1 오브젝트를 더 이상 어디에도 참조하지 않는다. → 최초의 o1 오브젝트는 방출(release)된다.
1.2 Memory Reference의 한계점
- 순환 참조일 경우 문제점이 발생한다.
1.3 순환참조??
function test() {
var a = {};
var b = {};
a.x = b;
b.x= a;
return "Test is running"
}
test()
👉 두 오브젝트가 생성되고 서로를 참조하면서 순환이 일어난다. 함수가 종료되면 오브젝트는 의미가 없어지므로 가비지 컬렉션이 수행되어야 하지만 두 오브젝트 각각 참조를 하고 있기 때문에 둘 다 가비지 컬렉션이 일어나지 않는 문제가 발생한다.
2. Mark-and-Sweep
✏️ 메모리 참조 방식을 개선할 수 있는 mark-and-sweep 방식!!
쉽게 설명하자면 조수가 조리대를 쭉 훑으면서 필요한 것들만 체크한 다음 체크되지 않은 것들은 버리는 방식이다. 프로그램 측면에서 말하자면 루트에서 닿지 않은 변수들을 가비지(garbage)처리 하는 것이다.
이 방식은 순환 참조가 발생해도 root와의 참조가 없는 경우 실제로 더 이상 사용하지 않을 것으로 판단이 가능하다는 장점을 가지고 있다.
2.1 동작 방식
- 가비지 콜렉터가 'roots'의 목록을 생성한다. (루트는 보통 전역 변수를 의미한다.)
- 자바스크립트는 window 객체가 대표적인 전역 변수의 예로 'root'로 작동한다.
- 알고리즘은 모든 루트들의 자식들을 검사한다.
- 어떤걸 검사해?
- 루트의 자식, 루트의 자식의 자식 이러한 방식으로 쭉 연결되어 있는지 아닌지를 검사하고 연결되어 있으면 체크하고 연결되어 있지 않는 경우에는 가비지(garbage)처리 해버린다.
- 어떤걸 검사해?
- 검사를 통하여 체크되어 있지 않는 것들은 가비지(garbage)로 판단하여 매모리에서 해제시키며 OS로 반환한다.
👉 정리하자면 도달 가능한 메모리는 active로 표시하고 나머지는 가비지(garbage)로 간주하여 메모리에서 해제시킨다.
2.2 한계점
✏️ 우리는 가비지 컬렉터가 언제 컬렉팅 작업을 하는지 알 수 없다는 것이다.
ex) 수많은 변수가 할당된다. 가비지(garbage)인 변수들이 메모리에 할당된다. 더 이상 어떠한 할당도 일어나지 않는다.
→ 메모리의 할당이 일어나야 root가 생기는데 할당이 일어나지 않으면 root가 만들어지지 않기 때문에 어떤 것이 가비지 인지 판단이 불가능하게 되는 것이다.
흔한 자바스크립트 메모리 누수
1. 의도치 않는 전역 변수
✏️ 자바스크립트가 만들어진 이유 중 하나는 초보자들이 사용할 수 있는 관대한 언어를 만드는 것이었다. 선언하지 않은 변수에 대한 참조는 전역 객체 내부에서 새로운 변수를 생성한다. (브라우저의 경우 전역 객체는 'window')
[코드 1]
function test (arg) {
a = '안녕하세요'
}
[코드 2]
function test (arg) {
window.a = '안녕하세요'
}
✏️ 코드 1과 코드 2는 동일하다. 변수 a가 test 함수 범위 안에서만 변수를 참조하도록 하였는데, var이나 let을 깜박하면 예상하지 못한 함수 내부에서 전역 변수를 생성하게 되는 것이다. 또 다른 실수는 this이다.
[코드 3]
function test() {
this.a = '안녕하세요'
}
test()
✏️ test 함수가 호출되면 의도치 않게 this는 전역 객체인 'window'를 가르키게 된다.
👉 함수가 실행이 되고 난 후 함수 내부에서 사용된 변수가 메모리에서 해제되어야 하는데 사용되고 있지 않음에도 불구하고 방출(release)되지 않는다. 불필요한 메모리 할당이 일어나고 있는 것이다.
이러한 실수를 방지하기 위해서는 변수 선언 키워드인 var, let, const를 사용해주자. 또 다른 방법으로는 'use strict'를 자바스크립트 파일 맨 상단에 선언하면 된다. 이는 자바스크립트를 더 엄격한 모드에서 파싱(Parsing)하게 함으로써 실수를 방지하게 만드는 것이다.
예상치 못한 전역 변수를 사용하는 것 뿐만 아니라 많은 양의 데이터를 임시로 저장하고 처리하는데 전역 변수를 사용하게 된다면 반드시 고려해봐야 할 문제이다. (메모리 소비량이 크기 때문이다.) 만약 전역 변수에 큰 데이터가 들어가 있다면, 모든 작업이 끝난 후 null 처리하거나 재할당 해주는 것이 필요하다.
2. 잊혀진 타이머 또는 콜백
자바스크립트를 사용하다 보면 setInterval 함수를 자주 사용하게 된다.
var data = getData();
setInterval(function(){
var result = document.getElementById('result');
if(result) {
result.innerHTML = data;
}
}, 1000)
✏️ 다음 코드를 보면 setInterval 내부에서 외부에서 가져온 data를 result의 innerHTML값을 변경하고 있다. 만약 result가 제거되면 setInterval 내부의 함수는 더 이상 필요 없게 된다. 그러나 타이머 시간에 따라 1초마다 계속해서 동작되며 가비지 콜렉팅이 발생하지 않는다. 따라서 data 변수가 많은 데이터를 가지고 있게 되면 많은 메모리를 낭비하고 있는 것이다.
✏️ 다음은 실제로 내가 작성했던 코드이다. 이 코드를 작성할 때 return문을 작성하지 않으면 어떻게 되는 걸까에 대해서 궁금해했던 적이 있다. 리스너를 제거하지 않으면 브라우저를 클릭할 때마다 이벤트 핸들러가 계속해서 생성되고 이는 전체 블록을 불필요하게 만들 수 있다. 하지만 모던 브라우저에서는 이러한 문제점을 올바르게 감지하고 처리할 수 있는 현대적인 가비지 콜렉팅 알고리즘을 사용하기 때문에 크게 신경 쓰지 않아도 되지만, 조금 유의할 필요는 있다고 할 수 있다.
3. 콘솔 로그
✏️ 우리가 실제 개발하면서 디버깅의 목적으로 콘솔을 출력하여 데이터가 잘 받아지고 있는지 확인하기 위한 용도로 콘솔 로그를 종종 찍는다. 받아오는 데이터를 쉽게 확인할 수 있는 것은 브라우저가 해당 데이터의 정보를 저장하기 때문에 가능한 것이다. 블록 실행이 종료된 후 메모리에서 해제되어야 하는 변수들이 콘솔에 출력되고 있기 때문에 가비지 콜렉팅이 일어나지 않게 된다. 따라서 콘솔 로그는 사용 후 가능한 삭제하는 것을 추천한다.
4. DOM 외부에서의 참조
var elements = {
button : document.getElementById('button'),
image : document.getElementById('image'),
text : document.getElementById('text'),
};
function remove() {
document.body.removeChild(document.getElementById('button'));
}
✏️ 다음 예시 코드를 보면서 이해 해보자. remove함수가 실행되면 DOM에서 button이 제거되지만, elements에서 계속해서 참조되고 있기 때문에 가비지 컬렉팅이 일어나지 않는다. 즉 메모리 낭비가 일어나고 있다는 것이다.
위의 코드는 간단하게 예시를 위해서 작성한 코드이기 때문에 쉽게 해결이 가능하다고 생각할 수 있지만 테이블 셀을 만들었을 때를 가정하자. 값이 할당된 셀 태그(td,tr)를 참조하고 있다가 테이블이 제거 되면 엄청난 메모리 누수를 유발할 수 있다. 반드시 해당 변수에 null값을 새로 할당해주거나 removechild를 통하여 제거가 필요하다.
'JavaScript' 카테고리의 다른 글
List & Array (2) | 2022.09.13 |
---|---|
자바스크립트 동작 원리 (0) | 2022.07.30 |
JavaScript - Event (0) | 2022.05.08 |
JavaScript - style property & class control (0) | 2022.05.08 |
JavaScript - DOM 접근하기 (0) | 2022.05.08 |
- Total
- Today
- Yesterday