jgjgill

WeakMap에 대해 알아보기

3 min read
Learn about weakmap thumbnail

들어가기

최근 Zustand를 사용할 일이 생겼다. 그래서 해당 라이브러리의 메인테이너이신 Daishi Kato님을 통해 관련 정보들을 습득하고 있다.

그중에서 HackDiary라는 가벼운 뉴스레터 컨텐츠로 주기적으로 Daishi Kato님의 지식과 생각들을 공유받고 있다. 여기서 최신 HackDiary에서 나의 지식의 구멍을 발견할 수 있었다.


이 HackDiary에서 핵심인 부분은 jotaistore를 모두 WeakMap으로 교체했다는 것이다. 기존에는 한 가지 케이스에 Map을 사용했었는데 이 부분이 메모리 누수를 유발할 수 있어서 이를 제거했다고 한다.


이를 읽으면서 MapWeakMap의 차이를 느끼지 못했다.

그래서 이번 기회에 MapWeakMap에 대해 알아보고자 한다.

가비지 컬렉션

MapWeakMap에 대해 이해하기 위해서는 가비지 컬렉션을 알아볼 필요가 있다.

JavaScript에서 메모리는 가바지 컬렉션을 통해 자동으로 관리가 된다.

객체가 생성되었을 때 자동으로 메모리를 할당하고 더 이상 사용되지 않을 때 자동으로 해제한다.


  • 필요할 때 할당
  • 할당된 메모리 사용
  • 필요하지 않으면 해제

여기서 주로 문제가 발생하는 부분은 필요하지 않으면 해제할 때이다.

이와 관련해서 간접적으로 가비지 컬렉션을 관찰하며 메모리 관리에 쓸 수 있는 데이터 구조가 WeakMap, WeakSet인 것이다.

Map

Map같은 경우 대부분의 사람들이 많이 접해서 익숙한 개념이라고는 생각이 든다.

그래서 이번 글에서는 간단하게 언급하는 정도로 넘어가고자 한다.


객체와 유사하게 생각하면 된다.

객체와의 주요 차이점으로는 Map같은 경우 키에 다양한 자료형을 허용한다는 것이다.

WeakMap

우선 WeakMap의 기본적인 특징에 대해 알아보자.


Map과 같이 키/값 쌍의 모음이다.

Map과 다른 점은 키같은 경우 반드시 객체 혹은 등록되지 않은 심볼(non-registered symbols)이어야 한다.

객체와 달리 원시값은 복사되어 컬렉션에 영원히 남을 수 있기 때문에 키값에 포함되지 않는다.

  • 원시값: 1 === 1
  • 객체: {} !== {}

일반 원시값을 추가하려고 하면 Uncaught TypeError: Invalid value used as weak map key 에러가 발생한다.

WeakMap 에러

여기서 등록되지 않는 심볼도 고유해서 다시 만들 수 없기에 객체와 같이 키값에 포함된다.

약한 참조

일반적으로 JavaScript에서 객체의 참조는 강하게 유지된다.

let jg = { nickname: 'jgjgill' }

let test = jg

jg = null // 참조를 null로 덮는다.

// jg 객체는 test 요소이기 때문에 가비지 컬렉터의 대상이 되지 않는다.
// test에 접근하면 객체를 얻을 수 있다.
console.log(test) // { nickname: "jgjgill" }


그래서 Map의 경우에도 객체가 강하게 참조된다.

참조를 null로 덮어써도 가비지 컬렉션의 대상이 되지 않는다.

let jg = { nickname: 'jgjgill' }

let map = new Map()
map.set(jg, '...')

jg = null // 참조를 null로 덮는다.

// jg 객체는 map 안에 저장되어 있다.
// map.keys()를 이용하면 해당 객체를 얻을 수 있다.

for (let obj of map.keys()) {
  console.log(obj) // { nickname: "jgjgill" }
}

console.log(map.size) // 1

Map 예제 코드

하지만 WeakMap객체를 약한 참조로 만들 수 있다.

이전 코드에서 MapWeakMap으로 바꿔보자.

let jg = { nickname: 'jgjgill' }

let weakMap = new WeakMap()
weakMap.set(jg, '...')

jg = null // 참조를 null로 덮는다.

// jg를 나타내는 객체는 메모리에서 지워진다.

WeakMap 예제 코드

객체가 WeakMap의 키에 포함되더라도 가비지 컬렉션의 대상이 된다.


주요 특징

키 객체가 가비지 컬렉션에 의해 정리된 후, 다른 곳에 강력한 참조가 존재하지 않으면 키에 연결된 값 또한 가비지 컬렉션의 대상이다.


또한 WeakMap키를 열거하는 기능을 제공하지 않는다. 키를 열거하는 메서드를 제공하게 되면 목록이 가비지 컬렉션 상태에 따라 달라질 수 있어 비결정성(non-determinism)이 발생한다. 이러한 경우에는 WeakMap 대신 Map을 사용해야 한다.


비결정성

여기서 비결정성이라는 말이 낮설다. GPT에게 도움을 요청했다.


WeakMap에서는 non-determinism이라는 단어도 쓰이는 것 같아. 어떤 의미야?



답변

WeakMap에서 non-determinism(결정 불가능성)이라는 용어는 주로 가비지 컬렉션과 관련이 있습니다.


여기서 결정 불가능성이란, 가비지 컬렉션 작업이 시스템 또는 런타임에 의해 자동으로 이뤄지며, 우리가 직접 조작할 수 없기 때문에 언제 어떤 순서로 메모리가 해제될지를 예측하기 어렵다는 것을 의미합니다.



GPT의 내용과 함께 생각해보면 WeakMap키의 활성 상태를 관찰하는 것을 허용하지 않도록 하여, 즉 비결정성을 막아서 메모리 누수의 위험성을 방지한 것이다.

마무리

확실히 대중적인 상태 관리 라이브러리는 메모리의 세세한 부분도 신경써야 한다는 점이 인상깊었다.

WeakMap에 대해 알아보면서도 참조와 관련된 개념들을 다시 복습할 수 있어서 의미있는 시간이 된 것 같다.

참고 문서

@2023 powered by jgjgill