메모리 관리 (Memory Management)

메모리 관리 (Memory Management)

자바스크립트에서 성능과 안정성을 유지하려면 메모리 관리가 필수다. 메모리 누수가 생기면 애플리케이션이 느려지거나 멈출 수 있다. 이번에는 메모리 관리 방법을 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


메모리를 잘 관리하면 리소스를 효율적으로 사용하고, 장시간 실행되는 앱에서도 안정성을 유지할 수 있다. 하나씩 살펴보자.


기본적인 메모리 할당과 해제

자바스크립트는 자동으로 메모리를 관리하지만, 이해가 필요하다:

function createData() {
    let bigArray = new Array(1000000).fill(0);
    return bigArray;
}

let data = createData();
data = null; // 참조 제거로 가비지 컬렉션 유도
console.log("메모리 해제 준비 완료");

변수를 null로 설정하면 참조가 끊어져 가비지 컬렉터가 메모리를 정리할 수 있다.


1. 메모리 누수 방지

전역 변수 사용을 줄이면 누수를 막을 수 있다:

// 잘못된 예
function leakMemory() {
    window.globalData = new Array(1000000).fill("leak");
}

// 개선된 예
function safeMemory() {
    const localData = new Array(1000000).fill("safe");
    return localData;
}

safeMemory();
console.log("로컬 데이터로 메모리 관리");

전역 변수 대신 로컬 변수를 사용하니 함수 종료 후 메모리가 자연스럽게 해제됐다.


2. 이벤트 리스너 정리

사용하지 않는 리스너를 제거하면 메모리 누수가 줄어든다:

function setupListener() {
    const button = document.querySelector("#btn");
    const handler = () => console.log("클릭");
    button.addEventListener("click", handler);

    return () => button.removeEventListener("click", handler);
}

const cleanup = setupListener();
cleanup(); // 리스너 제거
console.log("리스너 정리 완료");

이벤트를 제거하니 DOM 요소와 관련된 메모리가 해제될 가능성이 높아졌다.


3. WeakMap으로 약한 참조 관리

WeakMap을 사용하면 객체가 더 이상 필요 없을 때 자동으로 정리된다:

const cache = new WeakMap();

function storeData(obj) {
    if (!cache.has(obj)) {
        cache.set(obj, new Array(10000).fill(0));
    }
    return cache.get(obj);
}

let key = { id: 1 };
storeData(key);
key = null; // key 참조 제거
console.log("WeakMap으로 메모리 관리");

WeakMap은 키 객체가 참조를 잃으면 자동으로 메모리에서 제거되니 관리가 편해졌다.


4. 클로저와 메모리 누수

클로저가 필요 이상으로 메모리를 잡아두지 않게 주의하자:

// 누수 발생
function leakyClosure() {
    const largeData = new Array(1000000);
    return () => largeData[0];
}

// 개선
function safeClosure() {
    const largeData = new Array(1000000);
    const result = largeData[0];
    return () => result;
}

const fn = safeClosure();
console.log(fn());

필요한 데이터만 클로저에 저장하니 불필요한 메모리 점유가 줄었다.


5. 타이머 정리

타이머를 해제하지 않으면 메모리가 계속 쌓인다:

function startTimer() {
    const id = setInterval(() => {
        console.log("타이머 실행 중");
    }, 1000);
    return () => clearInterval(id);
}

const stop = startTimer();
setTimeout(stop, 5000);
console.log("타이머 정리 준비");

clearInterval로 타이머를 정리하니 메모리 사용이 최적화됐다.


6. 대용량 데이터 처리

큰 데이터를 조각내서 처리하면 메모리 부담이 줄어든다:

function processLargeArray(arr) {
    const chunkSize = 1000;
    let index = 0;

    function processChunk() {
        const end = Math.min(index + chunkSize, arr.length);
        for (; index < end; index++) {
            arr[index] *= 2;
        }
        if (index < arr.length) {
            setTimeout(processChunk, 0);
        }
    }

    processChunk();
}

const largeArray = new Array(1000000).fill(1);
processLargeArray(largeArray);

데이터를 나눠 처리하니 한 번에 메모리를 많이 잡지 않았다.


7. 객체 재사용

새 객체 생성을 줄이고 기존 객체를 재사용하면 효율적이다:

// 비효율적
function createPoint(x, y) {
    return { x, y };
}

// 효율적
const point = { x: 0, y: 0 };
function updatePoint(x, y) {
    point.x = x;
    point.y = y;
    return point;
}

console.log(updatePoint(5, 10));

객체를 재사용하니 가비지 컬렉션 부담이 줄었다.


8. 메모리 프로파일링

도구를 활용해 메모리 사용을 분석할 수 있다:

function trackMemory() {
    const data = new Array(1000000).fill("test");
    console.log(performance.memory.usedJSHeapSize);
    return data;
}

let result = trackMemory();
result = null;
console.log(performance.memory.usedJSHeapSize);

performance.memory로 사용량을 확인하니 메모리 상태를 파악할 수 있었다 (Chrome에서만 동작).


9. 서비스 워커와 메모리

무거운 작업을 서비스 워커로 옮기면 메인 스레드 부담이 줄어든다:

// sw.js
self.onmessage = (e) => {
    const result = new Array(1000000).fill(e.data);
    self.postMessage(result);
};

// main.js
const worker = new Worker("sw.js");
worker.onmessage = (e) => console.log("작업 완료");
worker.postMessage(1);

별도 스레드에서 작업하니 메인 스레드 메모리가 덜 부담스러웠다.


10. 가비지 컬렉션 유도

의도적으로 참조를 끊어 메모리 해제를 돕자:

function forceCleanup() {
    let temp = new Array(1000000).fill("temp");
    temp = null;
    if (globalThis.gc) globalThis.gc(); // Node.js에서 --expose-gc 옵션 필요
    console.log("정리 완료");
}

forceCleanup();

참조를 제거하고 강제로 가비지 컬렉션을 유도하니 메모리가 즉시 정리됐다 (특정 환경에서만 가능).


마무리

메모리 관리는 성능과 안정성을 높이는 데 핵심적인 역할을 한다. 누수 방지, 약한 참조, 타이머 정리, 서비스 워커 활용까지 상황에 맞는 방법을 적용하면 효율적인 애플리케이션을 만들 수 있다.


+ Recent posts