안녕하세요 웹 YB 이진혁입니다!
이번에는 이전에 단순히 불필요한 메모리를 삭제해주는 좋은 친구라고만 생각했던 가비지 컬렉션(GC)에 대해 JS 관점에서 알아보려고 합니다.
https://curryyou.tistory.com/275
(웹파트 세션 자료에 포함된 JS 메모리에 관련된 자료입니다!)
메모리 관리
프로그래밍 언어의 메모리 관리는 애플리케이션 성능과 안정성을 결정하는 중요한 요소이다. 자바스크립트를 사용해서 애플리케이션을 개발할 때도 이는 마찬가지이다. 예를 들어 리액트를 사용해 SPA(Single Page Application)을 구현하게 된다면 상호작용도 빈번하고 데이터가 계속해서 갱신된다면 메모리 관리가 필수적일 것이다.
프로그래밍 언어와 무관하게 메모리는 다음과 같은 생명주기를 가진다.
- 필요한 메모리를 할당한다.
- 필요한 메모리를 사용한다. (읽기, 쓰기)
- 해당 메모리가 필요 없어지면 메모리를 해제한다.
이때 메모리를 해제하는 과정이 개발자가 그 해제 시기를 결정해야 하는 로우레벨 언어(ex. C)가 있고, 프로그램이 더 이상 사용하지 않는 메모리를 자동으로 판단하고 회수하는 언어가 있다. 바로 이 자동으로 쓰지 않는 메모리를 회수하는 프로세스가 가비지 컬렉션(GC)이다.
그런데 더 이상 사용하지 않는 메모리를 자동으로 판단하는 기준이 뭘까? 간단한 예시를 살펴보자.
var object = { key: 'web' };
object = null; // 메모리에서 'object' 해제
위 코드에서 객체에 대한 참조를 null로 변경하면 해당 객체는 더 이상 접근할 수 없는 상태가 되기 때문에 가비지 컬렉터(GC)에 의해 메모리에서 해제가 된다. 그렇다면 왜 필요없는 메모리를 해제를 해줘야 하는 것일까? 그것은 메모리 누수와 연결된다.
메모리 누수
메모리 누수는 불필요한 데이터가 메모리를 차지한 상태로 유지되어 점점 더 많은 메모리를 소모하게 되는 현상.
특히 자바스크립트에서는 불필요한 객체, 이벤트 리스너, 클로저 등이 메모리에 계속 남아있는 메모리 누수가 자주 발생한다. 단순히 이벤트를 추가하기 위해 사용해 온 이벤트 리스터 역시 DOM 요소에 대한 참조를 유지하기 때문에, 해당 DOM 요소가 필요 없어진 후에도 메모리에 리스너가 남아있는 경우가 있다고 한다.
이런 경우도 또한 불필요한 객체 참조를 null로 만들거나, 필요 없어진 이벤트 리스너를 명시적으로 제거하는 removeEventListener를 사용하는 방법이 존재한다.
const handleClick = () => {
console.log('Hi');
}
var element = document.getElementById('element');
element.addEventListener('click', handleClick);
// 이벤트 리스너 제거
element.removeEventListener('click', handleClick);
이 외에도 대표적으로 아래의 상황에서 메모리 누수가 된다고 한다.
- 우발적으로 생성된 전역 변수
- 잊혀진 타이머 또는 콜백 함수
- DOM 외부에서 참조
- 특정한 경우의 Closure
이러한 상황을 살펴보니 왜 전역 변수 사용을 지양하라고 하는지 조금 이해가 간다. 함수 스코프 내에서 변수를 선언하고 사용한다면 그 함수가 종료 된다면 메모리가 해제되겠지만, 전역변수는 그렇지 않다고 한다. 전역 변수는 재할당하거나 null을 주지 않는 이상 회수되지 않기 때문이다. 또한 타이머를 설정하는 setInterval 함수도 타이머 기능을 구현할 때 사용한 적이 있는데 이 함수가 사용되지 않을 시점에 GC에 의해 수집되지 않기 때문에 메모리에 남아있는 경우도 있다고 한다.
물론 전역 변수의 경우 js파일 시작부에 'use strict'을 써서 전역 생성을 막는 등 명시적인 방법이 존재한다고 한다. 그렇기 때문에 GC가 있다고 해도 먼저 개발자가 코드를 작성할 때 메모리에 대한 생각을 하는게 좋은 방향인 것 같다.
가비지 컬렉션 (GC)
가비지 컬렉터(Garbage Collector)는 애플리케이션 실행 중 필요 없어져서 더 이상 접근할 수 없는 메모리(객체, 변수 등)를 자동으로 제거하여 메모리를 확보하고, 애플리케이션 성능을 최적화하는 프로세스
가비지 컬렉션(or 가비지 컬렉터)는 결국에 필요 없어진 메모리를 지워주는 역할을 한다. 이때 궁금했던 점이 바로 기준이다. 대체 어떻게 '필요 없다'라는 기준을 자동으로 판단하고 지워주는 것일까?
GC 알고리즘
GC 알고리즘에는 대표적으로 2가지가 있다. 참조-세기 (reference-counting)과 Mark-and-sweep이라고 부르는 알고리즘이다. 물론 최근에는 대부분 Mark-and-sweep를 쓴다고 하지만 이 두개를 다 살펴보고자 한다. 이유는 두 알고리즘이 앞에서 언급한 필요 없어진 메모리를 판단하는 기준이 다르기 때문에 이 기준을 잘 살펴볼 수 있을 것 같기 때문이다.
1. 참조-세기 (Reference-counting) 알고리즘
이 알고리즘은 "더 이상 필요 없는 객체"를 "어떤 다른 객체도 참조하지 않는 객체"라고 정의한다. 특정 객체를 참조하는 객체가 하나도 없다면, 그 객체에 대해 가비지 컬렉션을 수행한다.
말 그대로 다른 객체를 하나도 참조하지 않으면 그 객체는 더 이상 필요 없다고 GC가 판단해 메모리를 해제한다는 것이다. 하지만 이 참조-세기 알고리즘은 순환 참조라는 한계점을 가진다.
순환 참조에 대한 예시를 살펴보자.
const createCircularReference = () => {
let web = {};
let sopt = {};
web.ref = sopt; // web가 sopt를 참조
sopt.ref = web; // sopt가 web를 참조
console.log("순환 참조 생성됨:", web, sopt);
}
createCircularReference();
이 로직에 대한 과정은 아래와 같다.
- web과 sopt 두 개의 객체를 생성한다.
- web.ref는 sopt를 참조하고, sopt.ref는 web을 참조하도록 설정한다.
- 이로 인해 web과 sopt는 서로를 참조하는 순환 참조가 발생한다.
그렇다면 순환 참조가 왜 문제가 될까?
참조-세기 방식의 기준을 살펴보면 답이 된다. 해당 로직에서는 web과 sopt가 서로를 참조하고 있기 때문에 참조-세기 방식의 기준이었던 참조 유무에서 참조하고 있다고 판단이 된다. 즉, 두 객체는 가비지 컬렉터에 의해 수집되지 않고, 메모리에 남아 있는 메모리 누수가 발생하게 되는 것이다.
메모리 누수를 없애기 위해 GC를 이용하는 것인데 이를 잡지 못하는 이 알고리즘은 한계를 가진 것이다.
1. 마크 앤 스윕 (mark-and-sweep) 알고리즘
이 알고리즘은 "닿을 수 없는 객체"를 "필요 없는 객체"라고 규정짓는 알고리즘이다.
이 알고리즘은 이름에서 알 수 있듯이 무언가에 표시(mark)를 하고, 정리(sweep)하는 알고리즘이다. 이는 기존의 참조-세기와 다른 기준인 "닿을 수 없는 객체", 즉 도달 가능성이라는 개념을 통해 메모리의 필요 여부를 판단한다. 도달 가능한 객체를 '마크'하고, 도달할 수 없는 객체를 메모리에서 '스윕'해 제거하는 방식을 뜻한다고 한다. 이 기준으로 순환 참조의 한계를 해결하고 자바스크립트 GC의 대표적인 알고리즘으로 자리잡고 있다.
동작 과정
- Mark
- 객체가 생성될 때마다 mark bit가 0 (false)로 생성된다. mark 단계에서 모든 접근 가능한 객체의 mark bit가 1 (true)로 설정된다.
- Sweep
- 단계 후에 mark bit가 여전히 0 (false)로 설정된 객체들은 도달할 수 없는 객체이므로 가비지 컬렉터가 수집해 메모리에서 해제가 된다.
쉽게 이해하면 root부터 페인트를 들이부어서 채워지지 않는 객체의 메모리를 해제한다고 생각하면 편하다. 이걸 이해하는 것도 생각보다 오래 걸렸지만, 더 깊게 이해하려면 웹 브라우저가 이 GC를 최적화해서 사용한다고 하는데 이를 더 공부해보면 좋을 것 같다!!
[ 읽어보면 좋을만한 아티클 ]
https://gdsc-cau.github.io/articles/javascript-deep_study-gabage_collection/
https://ko.javascript.info/garbage-collection
https://fe-developers.kakaoent.com/2022/220519-garbage-collection/ (kakao FE 기술 블로그)
[ 참고한 아티클 ]
https://aroundthistime.tistory.com/83
https://kellis.tistory.com/140
https://f-lab.kr/insight/understanding-memory-management-in-javascript
'2주차' 카테고리의 다른 글
[JS] 스코프(Scope) (0) | 2024.10.29 |
---|---|
🤫 NaN을 조심하세요... (0) | 2024.10.29 |
[JS] localStorage 사용법을 알아보자. (0) | 2024.10.29 |
함수형 프로그래밍 찍먹 (0) | 2024.10.29 |
[Javascript] Webpack & Babel (0) | 2024.10.29 |