(λ²ˆμ—­) πŸ”₯ μžλ°”μŠ€ν¬λ¦½νŠΈ λ©”λͺ¨λ¦¬ 관리: 일반적인 λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜κ³  μ„±λŠ₯을 κ°œμ„ ν•˜λŠ” 방법

원문: JavaScript Memory Management: How to Avoid Common Memory Leaks and Improve Performance

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ΅œμ ν™”ν•˜λŠ” 데 도움이 λ˜λŠ” JS의 λ©”λͺ¨λ¦¬ 관리에 λŒ€ν•΄ μ„€λͺ…ν•©λ‹ˆλ‹€.

javascript memory leak

λͺ©μ°¨

λ„μž…

μ›Ή κ°œλ°œμžλŠ” μž‘μ„±ν•˜λŠ” λͺ¨λ“  μ½”λ“œκ°€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯에 영ν–₯을 λ―ΈμΉ  수 μžˆλ‹€λŠ” 것을 μ•Œκ³  μžˆμŠ΅λ‹ˆλ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 집쀑해야 ν•  κ°€μž₯ μ€‘μš”ν•œ μ˜μ—­ 쀑 ν•˜λ‚˜λŠ” λ©”λͺ¨λ¦¬ κ΄€λ¦¬μž…λ‹ˆλ‹€.

μ‚¬μš©μžκ°€ μ›Ήμ‚¬μ΄νŠΈμ™€ μƒν˜Έμž‘μš©ν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ 객체, λ³€μˆ˜, ν•¨μˆ˜κ°€ μƒμ„±λœλ‹€κ³  μƒκ°ν•΄λ³΄μ„Έμš”. μ£Όμ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μ΄λŸ¬ν•œ 객체가 μŒ“μ—¬ λΈŒλΌμš°μ €μ˜ λ©”λͺ¨λ¦¬λ₯Ό 막고 전체 μ‚¬μš©μž κ²½ν—˜μ„ 느리게 λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 마치 정보 κ³ μ†λ„λ‘œμ˜ ꡐ톡 체증과 κ°™μ•„μ„œ μ‚¬μš©μžλ₯Ό μ΄νƒˆν•˜κ²Œ λ§Œλ“œλŠ” λ‹΅λ‹΅ν•œ 병λͺ© ν˜„μƒμž…λ‹ˆλ‹€.

μ΄λŸ¬ν•œ ν˜„μƒμ„ κΌ­ κ²ͺ을 ν•„μš”λŠ” μ—†μŠ΅λ‹ˆλ‹€. μ˜¬λ°”λ₯Έ 지식과 κΈ°μˆ μ„ ν™œμš©ν•˜λ©΄ μžλ°”μŠ€ν¬λ¦½νŠΈ λ©”λͺ¨λ¦¬λ₯Ό μ œμ–΄ν•˜κ³ , μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ›ν™œν•˜κ³  효율적으둜 μ‹€ν–‰λ˜λ„λ‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 κΈ€μ—μ„œλŠ” λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 일반적인 원인과 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•œ μ „λž΅μ„ 포함해 μžλ°”μŠ€ν¬λ¦½νŠΈ λ©”λͺ¨λ¦¬ κ΄€λ¦¬μ˜ μ „λ°˜μ μΈ λ‚΄μš©μ„ μ‚΄νŽ΄λ΄…λ‹ˆλ‹€. μ „λ¬Έκ°€λ“  초보 κ°œλ°œμžλ“  κΉ”λ”ν•˜κ³  κ°„κ²°ν•˜λ©° λΉ λ₯Έ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 방법을 더 깊이 μ΄ν•΄ν•˜κ²Œ 될 κ²ƒμž…λ‹ˆλ‹€.

μžλ°”μŠ€ν¬λ¦½νŠΈ λ©”λͺ¨λ¦¬ κ΄€λ¦¬μ˜ 이해

1. 가비지 컬렉터

μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진은 가비지 컬렉터λ₯Ό μ‚¬μš©ν•˜μ—¬ 더 이상 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”λͺ¨λ¦¬λ₯Ό ν™•λ³΄ν•©λ‹ˆλ‹€. 가비지 μ»¬λ ‰ν„°μ˜ 역할은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ 더 이상 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 객체λ₯Ό μ‹λ³„ν•˜κ³  μ œκ±°ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. 가비지 μ»¬λ ‰ν„°λŠ” μ½”λ“œμ˜ 객체와 λ³€μˆ˜λ₯Ό μ§€μ†μ μœΌλ‘œ λͺ¨λ‹ˆν„°λ§ν•˜κ³  μ–΄λ–€ 객체가 μ—¬μ „νžˆ 참쑰되고 μžˆλŠ”μ§€ μΆ”μ ν•˜μ—¬ 이λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. 가비지 μ»¬λ ‰ν„°λŠ” μ‚¬μš©λ˜μ§€ μ•ŠλŠ” 객체λ₯Ό μ‚­μ œν•  λŒ€μƒμœΌλ‘œ ν‘œμ‹œν•˜κ³  객체가 μ‚¬μš© μ€‘μ΄λ˜ λ©”λͺ¨λ¦¬λ₯Ό ν™•λ³΄ν•©λ‹ˆλ‹€.

가비지 μ»¬λ ‰ν„°λŠ” β€ν‘œμ‹œ(mark) 및 정리(sweep)β€œλΌλŠ” κΈ°μˆ μ„ μ‚¬μš©ν•˜μ—¬ λ©”λͺ¨λ¦¬λ₯Ό κ΄€λ¦¬ν•©λ‹ˆλ‹€. 아직 μ‚¬μš© 쀑인 λͺ¨λ“  객체λ₯Ό ν‘œμ‹œν•œ λ‹€μŒ, νž™μ„ β€™μ •λ¦¬β€˜ν•˜μ—¬ μ‚¬μš©μ€‘μœΌλ‘œ ν‘œμ‹œλ˜μ§€ μ•Šμ€ 객체λ₯Ό λͺ¨λ‘ μ œκ±°ν•©λ‹ˆλ‹€. 이 ν”„λ‘œμ„ΈμŠ€λŠ” 주기적으둜 μˆ˜ν–‰λ˜λ©° νž™μ˜ λ©”λͺ¨λ¦¬κ°€ λΆ€μ‘±ν•  λ•Œλ„ μˆ˜ν–‰λ˜μ–΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ΄ 항상 μ΅œλŒ€ν•œ 효율적으둜 μœ μ§€λ˜λ„λ‘ ν•©λ‹ˆλ‹€.

2. μŠ€νƒ vs νž™

μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ λ©”λͺ¨λ¦¬λŠ” μŠ€νƒκ³Ό νž™μ΄λΌλŠ” 두 가지 μ£Όμš” μš”μ†Œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

μŠ€νƒμ€ ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜λŠ” λ™μ•ˆμ—λ§Œ ν•„μš”ν•œ 데이터λ₯Ό μ €μž₯ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€. λΉ λ₯΄κ³  νš¨μœ¨μ μ΄μ§€λ§Œ μš©λŸ‰μ΄ μ œν•œλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. ν•¨μˆ˜κ°€ 호좜되면 μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진은 ν•¨μˆ˜μ˜ λ³€μˆ˜μ™€ λ§€κ°œλ³€μˆ˜λ₯Ό μŠ€νƒμœΌλ‘œ λ°€μ–΄λ„£κ³ , ν•¨μˆ˜κ°€ λ°˜ν™˜λ˜λ©΄ λ‹€μ‹œ μŠ€νƒμ—μ„œ κΊΌλƒ…λ‹ˆλ‹€. μŠ€νƒμ€ λΉ λ₯Έ μ•‘μ„ΈμŠ€μ™€ λΉ λ₯Έ λ©”λͺ¨λ¦¬ 관리λ₯Ό μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€.

반면 νž™μ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 전체 수λͺ… λ™μ•ˆ ν•„μš”ν•œ 데이터λ₯Ό μ €μž₯ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€. μŠ€νƒλ³΄λ‹€ 쑰금 느리고 덜 μ²΄κ³„μ μ΄μ§€λ§Œ μš©λŸ‰μ΄ 훨씬 ν½λ‹ˆλ‹€. νž™μ€ μ—¬λŸ¬ 번 μ•‘μ„ΈμŠ€ν•΄μ•Ό ν•˜λŠ” 객체, λ°°μ—΄ 및 기타 λ³΅μž‘ν•œ 데이터 ꡬ쑰λ₯Ό μ €μž₯ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.

λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 일반적인 원인

λ©”λͺ¨λ¦¬ λˆ„μˆ˜λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μž μž…ν•΄ μ„±λŠ₯ 문제λ₯Ό μΌμœΌν‚€λŠ” κ΅ν™œν•œ 적이 될 수 μžˆλ‹€λŠ” 것을 잘 μ•Œκ³  계싀 κ²ƒμž…λ‹ˆλ‹€. λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 일반적인 원인을 μ΄ν•΄ν•˜λ©΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λŠ” 데 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€.

1. μˆœν™˜ μ°Έμ‘°

λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ κ°€μž₯ 일반적인 원인 쀑 ν•˜λ‚˜λŠ” μˆœν™˜ μ°Έμ‘°μž…λ‹ˆλ‹€. μˆœν™˜ μ°Έμ‘°λŠ” 두 개 μ΄μƒμ˜ 객체가 μ„œλ‘œλ₯Ό μ°Έμ‘°ν•˜μ—¬ 가비지 컬렉터가 λŠμ„ 수 μ—†λŠ” μˆœν™˜μ„ 생성할 λ•Œ λ°œμƒν•©λ‹ˆλ‹€. 이둜 인해 객체가 더 이상 ν•„μš”ν•˜μ§€ μ•Šκ²Œ λ˜μ–΄λ„ μ˜€λž«λ™μ•ˆ λ©”λͺ¨λ¦¬μ— μœ μ§€λ  수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒμ€ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

let object1 = {};
let object2 = {};

// 객체1κ³Ό 객체2 사이에 μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
object1.next = object2;
object2.prev = object1;

// object1κ³Ό object2둜 무언가λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
// ...

// μˆœν™˜ μ°Έμ‘°λ₯Ό 끊으렀면 object1κ³Ό object2λ₯Ό null둜 μ„€μ •ν•©λ‹ˆλ‹€.
object1 = null;
object2 = null;

이 μ˜ˆμ œμ—μ„œλŠ” object1κ³Ό object2λΌλŠ” 두 개의 객체λ₯Ό μƒμ„±ν•˜κ³  next 및 prev ν”„λ‘œνΌν‹°λ₯Ό μΆ”κ°€ν•˜μ—¬ 두 객체 사이에 μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ object1κ³Ό object2λ₯Ό null둜 μ„€μ •ν•˜μ—¬ μˆœν™˜ μ°Έμ‘°λ₯Ό λŠμ§€λ§Œ, 가비지 컬렉터가 μˆœν™˜ μ°Έμ‘°λ₯Ό λŠμ„ 수 μ—†κΈ° λ•Œλ¬Έμ— 객체가 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ€ 후에도 μ˜€λž«λ™μ•ˆ λ©”λͺ¨λ¦¬μ— μœ μ§€λ˜μ–΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μœ ν˜•μ˜ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λ €λ©΄ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ delete ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•˜λŠ” ν”„λ‘œνΌν‹°λ₯Ό μ œκ±°ν•˜λŠ” "μˆ˜λ™ λ©”λͺ¨λ¦¬ 관리"λΌλŠ” κΈ°μˆ μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

delete object1.next;
delete object2.prev;

μ΄λŸ¬ν•œ μœ ν˜•μ˜ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λŠ” 또 λ‹€λ₯Έ 방법은 객체와 λ³€μˆ˜μ— λŒ€ν•œ μ•½ν•œ μ°Έμ‘°λ₯Ό 생성할 수 μžˆλŠ” WeakMapκ³Ό WeakSet을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. 이 μ˜΅μ…˜μ— λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ 이 κΈ€μ˜ λ’·λΆ€λΆ„μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

2. 이벀트 λ¦¬μŠ€λ„ˆ

λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 또 λ‹€λ₯Έ 일반적인 원인은 이벀트 λ¦¬μŠ€λ„ˆμž…λ‹ˆλ‹€. 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό μš”μ†Œμ— μ—°κ²°ν•˜λ©΄ λ¦¬μŠ€λ„ˆ ν•¨μˆ˜μ— λŒ€ν•œ μ°Έμ‘°κ°€ μƒμ„±λ˜μ–΄ 가비지 컬렉터가 μš”μ†Œμ—μ„œ μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό ν™•λ³΄ν•˜μ§€ λͺ»ν•˜κ²Œ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μš”μ†Œκ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ„ λ•Œ λ¦¬μŠ€λ„ˆ ν•¨μˆ˜λ₯Ό μ œκ±°ν•˜μ§€ μ•ŠμœΌλ©΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μ˜ˆμ‹œλ₯Ό μ°Έμ‘°ν•˜μ„Έμš”.

let button = document.getElementById('my-button');

// λ²„νŠΌ μš”μ†Œμ— 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό λΆ™μž…λ‹ˆλ‹€.
button.addEventListener('click', function() {
  console.log('Button was clicked!');
});

// λ²„νŠΌμ΄ 무언가 λ™μž‘μ„ ν•©λ‹ˆλ‹€.
// ...

// DOMμ—μ„œ λ²„νŠΌμ„ μ œκ±°ν•©λ‹ˆλ‹€.
button.parentNode.removeChild(button);

이 μ˜ˆμ œμ—μ„œλŠ” λ²„νŠΌ μš”μ†Œμ— 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό μ—°κ²°ν•œ λ‹€μŒ DOMμ—μ„œ λ²„νŠΌμ„ μ œκ±°ν•©λ‹ˆλ‹€. λ²„νŠΌ μš”μ†Œκ°€ 더 이상 λ¬Έμ„œμ— μ—†μ§€λ§Œ 이벀트 λ¦¬μŠ€λ„ˆλŠ” μ—¬μ „νžˆ μ—°κ²°λ˜μ–΄ μžˆμœΌλ―€λ‘œ λ¦¬μŠ€λ„ˆ ν•¨μˆ˜μ— λŒ€ν•œ μ°Έμ‘°κ°€ μƒμ„±λ˜μ–΄ 가비지 컬렉터가 μš”μ†Œμ—μ„œ μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό ν™•λ³΄ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€. μš”μ†Œκ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ„ λ•Œ λ¦¬μŠ€λ„ˆ ν•¨μˆ˜λ₯Ό μ œκ±°ν•˜μ§€ μ•ŠμœΌλ©΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μœ ν˜•μ˜ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λ €λ©΄ μš”μ†Œκ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ„ λ•Œ 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό μ œκ±°ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

button.removeEventListener('click', function() {
  console.log('Button was clicked!');
});

또 λ‹€λ₯Έ 방법은 νŠΉμ • 이벀트 λŒ€μƒμ— μΆ”κ°€λœ λͺ¨λ“  이벀트 λ¦¬μŠ€λ„ˆλ₯Ό μ œκ±°ν•˜λŠ” EventTarget.removeAllListeners() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

button.removeAllListeners();

3. μ „μ—­ λ³€μˆ˜

λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ μ„Έ 번째 일반적인 원인은 μ „μ—­ λ³€μˆ˜μž…λ‹ˆλ‹€. μ „μ—­ λ³€μˆ˜λ₯Ό μƒμ„±ν•˜λ©΄ μ½”λ“œμ˜ μ–΄λŠ μœ„μΉ˜μ—μ„œλ‚˜ μ ‘κ·Όν•  수 μžˆμœΌλ―€λ‘œ 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ€ μ‹œμ μ„ νŒλ‹¨ν•˜κΈ° μ–΄λ €μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€. 이둜 인해 λ³€μˆ˜κ°€ ν•„μš”ν•˜μ§€ μ•Šκ²Œ λ˜μ–΄λ„ μ˜€λž«λ™μ•ˆ λ©”λͺ¨λ¦¬μ— μœ μ§€λ  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

// μ „μ—­ λ³€μˆ˜λ₯Ό μ„ μ–Έν•©λ‹ˆλ‹€.
let myData = {
  largeArray: new Array(1000000).fill('some data'),
  id: 1,
};

// myData λ³€μˆ˜λ‘œ 무언가λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
// ...

// μ°Έμ‘°λ₯Ό 끊기 μœ„ν•΄ myDataλ₯Ό null둜 μ„€μ •ν•©λ‹ˆλ‹€.
myData = null;

이 μ˜ˆμ œμ—μ„œλŠ” μ „μ—­ λ³€μˆ˜ myDataλ₯Ό μƒμ„±ν•˜κ³  κ·Έ μ•ˆμ— λŒ€λŸ‰μ˜ 데이터 배열을 μ €μž₯ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ μ°Έμ‘°λ₯Ό 끊기 μœ„ν•΄ myDataλ₯Ό null둜 μ„€μ •ν–ˆμ§€λ§Œ λ³€μˆ˜κ°€ μ „μ—­μ΄λ―€λ‘œ μ½”λ“œμ˜ μ–΄λŠ μœ„μΉ˜μ—μ„œλ‚˜ μ—¬μ „νžˆ μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ€ μ‹œμ μ„ νŒλ‹¨ν•˜κΈ° μ–΄λ ΅κΈ° λ•Œλ¬Έμ—, λ³€μˆ˜κ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•ŠμŒμ—λ„ μ˜€λž«λ™μ•ˆ λ©”λͺ¨λ¦¬μ— μœ μ§€λ˜μ–΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μΌμœΌν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μœ ν˜•μ˜ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λ €λ©΄ "ν•¨μˆ˜ μŠ€μ½”ν•‘" 기법을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 기법은 ν•¨μˆ˜λ₯Ό μƒμ„±ν•˜κ³  ν•΄λ‹Ή ν•¨μˆ˜ λ‚΄μ—μ„œ λ³€μˆ˜λ₯Ό μ„ μ–Έν•˜μ—¬ ν•΄λ‹Ή ν•¨μˆ˜μ˜ μŠ€μ½”ν”„ λ‚΄μ—μ„œλ§Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ ν•¨μˆ˜κ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•Šκ²Œ 될 λ•Œ, λ³€μˆ˜κ°€ μžλ™μœΌλ‘œ 가비지 μ»¬λ ‰νŒ…λ©λ‹ˆλ‹€.

function myFunction() {
  let myData = {
    largeArray: new Array(1000000).fill('some data'),
    id: 1,
  };

  // myData λ³€μˆ˜λ‘œ 무언가λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
  // ...
}
myFunction();

또 λ‹€λ₯Έ 방법은 var λŒ€μ‹  μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ letκ³Ό constλ₯Ό μ‚¬μš©ν•˜μ—¬ 블둝 μŠ€μ½”ν”„ λ³€μˆ˜λ₯Ό μƒμ„±ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. letκ³Ό const둜 μ„ μ–Έλœ λ³€μˆ˜λŠ” ν•΄λ‹Ή λ³€μˆ˜κ°€ μ •μ˜λœ 블둝 λ‚΄μ—μ„œλ§Œ μ ‘κ·Όν•  수 있으며 μŠ€μ½”ν”„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μžλ™μœΌλ‘œ 가비지 μ»¬λ ‰νŒ…λ©λ‹ˆλ‹€.

{
  let myData = {
    largeArray: new Array(1000000).fill('some data'),
    id: 1,
  };

  // myData λ³€μˆ˜λ‘œ 무언가λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
  // ...
}

μˆ˜λ™ λ©”λͺ¨λ¦¬ 관리 λͺ¨λ²” 사둀

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ„ κ΄€λ¦¬ν•˜λŠ” 데 도움이 λ˜λŠ” λ©”λͺ¨λ¦¬ 관리 도ꡬ와 기법을 μ œκ³΅ν•©λ‹ˆλ‹€.

1. μ•½ν•œ μ°Έμ‘° μ‚¬μš©

μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ κ°€μž₯ κ°•λ ₯ν•œ λ©”λͺ¨λ¦¬ 관리 도ꡬ 쀑 ν•˜λ‚˜λŠ” WeakMapκ³Ό WeakSetμž…λ‹ˆλ‹€. 이듀은 객체와 λ³€μˆ˜μ— λŒ€ν•œ μ•½ν•œ μ°Έμ‘°λ₯Ό 생성할 수 μžˆλŠ” 특수 데이터 κ΅¬μ‘°μž…λ‹ˆλ‹€. μ•½ν•œ μ°Έμ‘°λŠ” 가비지 컬렉터가 객체가 μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό ν™•λ³΄ν•˜μ§€ λͺ»ν•˜λ„둝 λ§‰λŠ”λ‹€λŠ” μ μ—μ„œ 일반 참쑰와 λ‹€λ¦…λ‹ˆλ‹€. λ”°λΌμ„œ μˆœν™˜ 참쑰둜 μΈν•œ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λŠ” 데 μœ μš©ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. λ‹€μŒμ€ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

let object1 = {};
let object2 = {};

// WeakMap을 μƒμ„±ν•©λ‹ˆλ‹€.
let weakMap = new WeakMap();

// object1을 WeakMap에 μΆ”κ°€ν•œ λ‹€μŒ
// object1에 WeakMap을 μΆ”κ°€ν•˜μ—¬ μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
weakMap.set(object1, 'some data');
object1.weakMap = weakMap;

// weakSet을 μƒμ„±ν•˜κ³  object2λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
let weakSet = new WeakSet();
weakSet.add(object2);

// 이 경우 가비지 μ»¬λ ‰ν„°λŠ” object1κ³Ό object2κ°€ μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό 확보할 수 μžˆμŠ΅λ‹ˆλ‹€.
// object1 κ³Ό object2의 μ°Έμ‘°κ°€ μ•½ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

이 μ˜ˆμ œμ—μ„œλŠ” object1κ³Ό object2λΌλŠ” 두 개의 객체λ₯Ό μƒμ„±ν•˜κ³  각각 WeakMapκ³Ό WeakSet에 μΆ”κ°€ν•˜μ—¬ 이듀 사이에 μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 객체에 λŒ€ν•œ μ°Έμ‘°κ°€ μ•½ν•˜κΈ° λ•Œλ¬Έμ— 가비지 μ»¬λ ‰ν„°λŠ” 객체가 계속 참쑰되고 μžˆμŒμ—λ„ λΆˆκ΅¬ν•˜κ³  객체가 μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό 확보할 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ μˆœν™˜ 참쑰둜 μΈν•œ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°©μ§€ν•˜λŠ” 데 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€.

2. 가비지 컬렉터 API μ‚¬μš©

또 λ‹€λ₯Έ λ©”λͺ¨λ¦¬ 관리 기법은 가비지 컬렉터 APIλ₯Ό μ‚¬μš©ν•˜μ—¬ μˆ˜λ™μœΌλ‘œ 가비지 μ»¬λ ‰μ…˜μ„ νŠΈλ¦¬κ±°ν•˜κ³  νž™μ˜ ν˜„μž¬ μƒνƒœμ— λŒ€ν•œ 정보λ₯Ό μ–»λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λŠ” λ©”λͺ¨λ¦¬ λˆ„μˆ˜ 및 μ„±λŠ₯ 문제λ₯Ό λ””λ²„κΉ…ν•˜λŠ” 데 μœ μš©ν•©λ‹ˆλ‹€. λ‹€μŒμ€ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

let object1 = {};
let object2 = {};

// object1κ³Ό object2 사이에 μˆœν™˜ μ°Έμ‘°λ₯Ό λ§Œλ“­λ‹ˆλ‹€.
object1.next = object2;
object2.prev = object1;

// μˆ˜λ™μœΌλ‘œ 가비지 컬렉터λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.
gc();

이 μ˜ˆμ œμ—μ„œλŠ” object1κ³Ό object2λΌλŠ” 두 개의 객체λ₯Ό μƒμ„±ν•˜κ³  next 및 prev ν”„λ‘œνΌν‹°λ₯Ό μΆ”κ°€ν•˜μ—¬ 두 객체 사이에 μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ gc() ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ 가비지 μ»¬λ ‰μ…˜μ„ μˆ˜λ™μœΌλ‘œ νŠΈλ¦¬κ±°ν•˜λ©΄ 객체가 계속 참쑰되고 μžˆμŒμ—λ„ λΆˆκ΅¬ν•˜κ³  객체가 μ‚¬μš©ν•˜λŠ” λ©”λͺ¨λ¦¬λ₯Ό 확보할 수 μžˆμŠ΅λ‹ˆλ‹€.

gc() ν•¨μˆ˜λŠ” λͺ¨λ“  μžλ°”μŠ€ν¬λ¦½νŠΈ μ—”μ§„μ—μ„œ μ§€μ›λ˜λŠ” 것은 μ•„λ‹ˆλ©°, 엔진에 따라 λ™μž‘μ΄ λ‹€λ₯Ό 수 μžˆλ‹€λŠ” 점에 μœ μ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ 가비지 μ»¬λ ‰μ…˜μ„ μˆ˜λ™μœΌλ‘œ νŠΈλ¦¬κ±°ν•˜λ©΄ μ„±λŠ₯에 영ν–₯을 λ―ΈμΉ  수 μžˆμœΌλ―€λ‘œ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” gc() ν•¨μˆ˜ 외에도 μžλ°”μŠ€ν¬λ¦½νŠΈ μ—”μ§„μ˜ global.gc() ν•¨μˆ˜μ™€ 일뢀 λΈŒλΌμš°μ € μ—”μ§„μ˜ performance.gc() ν•¨μˆ˜λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. 이 ν•¨μˆ˜λ“€μ€ νž™μ˜ ν˜„μž¬ μƒνƒœλ₯Ό ν™•μΈν•˜κ³  가비지 μ»¬λ ‰μ…˜ ν”„λ‘œμ„ΈμŠ€μ˜ μ„±λŠ₯을 μΈ‘μ •ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

3. νž™ μŠ€λƒ…μƒ·κ³Ό ν”„λ‘œνŒŒμΌλŸ¬ μ‚¬μš©ν•˜κΈ°

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•˜λŠ” 방식을 μ΄ν•΄ν•˜λŠ” 데 도움이 λ˜λŠ” νž™ μŠ€λƒ…μƒ·κ³Ό ν”„λ‘œνŒŒμΌλŸ¬λ„ μ œκ³΅ν•©λ‹ˆλ‹€. νž™ μŠ€λƒ…μƒ·μ„ μ‚¬μš©ν•˜λ©΄ νž™μ˜ ν˜„μž¬ μƒνƒœλ₯Ό μŠ€λƒ…μƒ·μœΌλ‘œ 찍고 이λ₯Ό λΆ„μ„ν•˜μ—¬ μ–΄λ–€ 객체가 κ°€μž₯ λ§Žμ€ λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•˜λŠ”μ§€ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒμ€ νž™ μŠ€λƒ…μƒ·μ„ μ‚¬μš©ν•˜μ—¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μ‹λ³„ν•˜λŠ” 방법에 λŒ€ν•œ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

// νž™ μŠ€λƒ…μƒ·μ„ μ‹œμž‘ν•©λ‹ˆλ‹€.
let snapshot1 = performance.heapSnapshot();

// λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μœ λ°œν•˜λŠ” μ–΄λ–€ λ™μž‘μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.
for (let i = 0; i < 100000; i++) {
  myArray.push({
    largeData: new Array(1000000).fill('some data'),
    id: i,
  });
}

// λ‹€λ₯Έ νž™ μŠ€λƒ…μƒ·μ„ μ‹€ν–‰ν•©λ‹ˆλ‹€.
let snapshot2 = performance.heapSnapshot();

// 두 μŠ€λƒ…μƒ·μ„ λΉ„κ΅ν•˜μ—¬ μ–΄λ–€ 객체가 μƒμ„±λ˜μ—ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.
let diff = snapshot2.compare(snapshot1);

// 차이λ₯Ό λΆ„μ„ν•˜μ—¬ μ–΄λ–€ 객체가 κ°€μž₯ λ§Žμ€ λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•˜λŠ”μ§€ 확인 ν•©λ‹ˆλ‹€.
diff.forEach(function(item) {
  if (item.size > 1000000) {
    console.log(item.name);
  }
});

이 μ˜ˆμ œμ—μ„œλŠ” λŒ€μš©λŸ‰ 데이터λ₯Ό λ°°μ—΄λ‘œ ν‘Έμ‹œν•˜λŠ” 루프λ₯Ό μ‹€ν–‰ν•˜κΈ° 전후에 두 개의 νž™ μŠ€λƒ…μƒ·μ„ μƒμ„±ν•œ λ‹€μŒ, 두 μŠ€λƒ…μƒ·μ„ λΉ„κ΅ν•˜μ—¬ 루프 쀑에 μƒμ„±λœ 객체듀을 ν™•μΈν•©λ‹ˆλ‹€. 그런 λ‹€μŒ 차이λ₯Ό λΆ„μ„ν•˜μ—¬ μ–΄λ–€ 객체가 κ°€μž₯ λ§Žμ€ λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•˜λŠ”μ§€ 확인할 수 있으며, 이λ₯Ό 톡해 λŒ€μš©λŸ‰ λ°μ΄ν„°λ‘œ μΈν•œ λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μ‹λ³„ν•˜λŠ” 데 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€.

ν”„λ‘œνŒŒμΌλŸ¬λ₯Ό μ‚¬μš©ν•˜λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯을 μΆ”μ ν•˜κ³  λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ΄ λ§Žμ€ μ˜μ—­μ„ 식별할 수 μžˆμŠ΅λ‹ˆλ‹€.

let profiler = new Profiler();

profiler.start();

// λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μœ λ°œν•˜λŠ” μ–΄λ–€ λ™μž‘μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.
for (let i = 0; i < 100000; i++) {
  myArray.push({
    largeData: new Array(1000000).fill('some data'),
    id: i,
  });
}

profiler.stop();

let report = profiler.report();

// λ³΄κ³ μ„œλ₯Ό λΆ„μ„ν•˜μ—¬ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ΄ λ§Žμ€ μ˜μ—­μ„ μ‹λ³„ν•©λ‹ˆλ‹€.
for (let func of report) {
  if (func.memory > 1000000) {
    console.log(func.name);
  }
}

이 μ˜ˆμ œμ—μ„œλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œνŒŒμΌλŸ¬λ₯Ό μ‚¬μš©ν•˜μ—¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯ 좔적을 μ‹œμž‘ 및 μ€‘μ§€ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ³΄κ³ μ„œμ—λŠ” 호좜된 ν•¨μˆ˜μ™€ 각 ν•¨μˆ˜μ— λŒ€ν•œ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ— λŒ€ν•œ 정보가 ν‘œμ‹œλ©λ‹ˆλ‹€.

νž™ μŠ€λƒ…μƒ·κ³Ό ν”„λ‘œνŒŒμΌλŸ¬λŠ” λͺ¨λ“  μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진과 λΈŒλΌμš°μ €μ—μ„œ μ§€μ›λ˜λŠ” 것은 μ•„λ‹ˆλ―€λ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ‚¬μš©ν•˜κΈ° 전에 ν˜Έν™˜μ„±μ„ ν™•μΈν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

κ²°λ‘ 

μ΄μ œκΉŒμ§€ 가비지 μ»¬λ ‰μ…˜ ν”„λ‘œμ„ΈμŠ€, λ‹€μ–‘ν•œ λ©”λͺ¨λ¦¬ μœ ν˜•, μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λ©”λͺ¨λ¦¬ 관리 도ꡬ와 기법 λ“± μžλ°”μŠ€ν¬λ¦½νŠΈ λ©”λͺ¨λ¦¬ κ΄€λ¦¬μ˜ 기본을 μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. λ˜ν•œ λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 일반적인 원인에 λŒ€ν•΄ μ„€λͺ…ν•˜κ³  이λ₯Ό λ°©μ§€ν•˜λŠ” 방법에 λŒ€ν•œ μ˜ˆμ‹œλ₯Ό μ œκ³΅ν–ˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ λ©”λͺ¨λ¦¬ 관리 λͺ¨λ²” 사둀λ₯Ό μ΄ν•΄ν•˜κ³  κ΅¬ν˜„ν•˜λŠ” 데 μ‹œκ°„μ„ νˆ¬μžν•˜λ©΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜ κ°€λŠ₯성을 쀄이며 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. 이 글이 도움이 λ˜μ—ˆλ‹€λ©΄ λ°•μˆ˜λ₯Ό λ³΄λ‚΄μ£Όμ…”μ„œ μ‘μ›ν•΄μ£Όμ‹œκ³ , Mediumμ—μ„œ μ €λ₯Ό νŒ”λ‘œμš°ν•˜μ—¬ κΈ°μˆ μ„ ν•œ 단계 더 λ°œμ „μ‹œν‚€λŠ” 데 도움이 λ˜λŠ” μƒˆλ‘œμš΄ κΈ€κ³Ό ν•™μŠ΅ κΈ°νšŒμ— λŒ€ν•œ μ΅œμ‹  μ†Œμ‹μ„ λ°›μ•„λ³΄μ„Έμš”.

νŒ”λ‘œμš°

ꡬ독

πŸš€ ν•œκ΅­μ–΄λ‘œ 된 ν”„λŸ°νŠΈμ—”λ“œ 아티클을 λΉ λ₯΄κ²Œ 받아보고 μ‹Άλ‹€λ©΄ Korean FE Article(https://kofearticle.substack.com/)을 κ΅¬λ…ν•΄μ£Όμ„Έμš”!


Written by@[Ykss]
고이게 두지 μ•Šκ³  ν˜λ €λ³΄λ‚΄λŠ” κ°œλ°œμžκ°€ 되자.

GitHubInstagramLinkedIn