일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 만조니
- 영어문장
- 20문장
- Python
- 아보카도 색깔
- Trump
- 원어민표현
- 영어20문장
- 영어표현
- good avocado
- Trudeau
- 오블완
- avocado color
- 파이썬
- 2025 year
- 두번째삶
- 영어문장20개
- 영어회화공부
- 영어공부
- 루이지만조니
- 영어표현20
- prime minister justin trudeau
- 원어민영어표현
- 티스토리챌린지
- 파이썬공부
- secondlife
- 자주쓰는영어회화
- 영어회화
- 윤석열탄핵소추안가결
- 영어회화암기
- Today
- Total
쿨가이두번째삶
study coding(memory leak) 본문
개발 공부
비록 비전공자이지만 요즘은 온라인에 많은 정보가 있으니 앞으로 도전하게 될 second life에 가기 위해서 코딩 공부를 도전해보려고 한다.
Memory Leak
프로그램에서 사용했었다가 더 이상 필요하지 않지만 아직 OS나 자유메모리 풀에 반환되지 않은 메모리 조각들을 말합니다.
Java의 Memory Leaks(메모리 누수)
자바스크립트(JavaScript)에서 Memory leak에 대해 크게 4가지로 나와있다
1: 전역 변수
자바스크립트는 흥미로운 방식으로 선언되지 않은 변수를 처리합니다. 선언되지 않은 변수가 참조되면 전역 객체에 새로운 변수를 생성하는 것입니다. 브라우저상이라면 전역 객체는 window가 됩니다. 따라서, function foo(arg) { bar = "some text"; }는 다음과 동일합니다. function foo(arg) { window.bar = "some text"; } bar의 목적이 foo 함수 내의 어떤 변수를 가리키는 것이었다고 해봅시다. 하지만 var를 사용하여 변수를 선언하지 않으므로써 필요 없는 전역 변수가 생성될 것입니다. 위의 예에서는 이게 큰 손실을 끼치지는 않습니다. 하지만 더욱 위험한 경우도 생각해 볼 수 있을 것입니다. 또한 this를 이용해서도 뜻하지 않은 전역 변수를 생성할 수 있습니다. function foo() { this.var1 = "potential accidental global"; } // 다른 함수 내에 있지 않은 foo를 호출하면 this는 글로벌 객체(window)를 가리킴 foo(); 자바스크립트 파일의 상단에 use strict를 사용하면 위와 같은 모든 것들을 회피할 수 있습니다. 이 모드에서 자바스크립트는 예상치 못한 전역 변수 생성을 방지할 수 있는 훨씬 엄격한 파싱을 시도합니다. 기대치 않게 생성된 전역 변수는 물론 문제입니다만 많은 경우에는 의도적으로 가비지 컬렉터가 정리할 수 없는 전역 변수를 사용하기도 합니다. 임시로 정보를 저장하거나 많은 양의 정보를 처리할 때 사용하는 전역 변수에 특별히 세심한 주의를 기울일 필요가 있습니다. 꼭 그래야 한다면 전역 변수를 사용할 수도 있지만 사용을 마친 다음에는 꼭 null로 할당하거나 다른 변수로 할당하시기 바랍니다.
2: 잊혀진 타이머 혹은 콜백 함수
자바스크립트에서 많이 사용되는 setInterval을 예로 들어보겠습니다. 옵저버를 제공하는 라이브러리나 콜백을 받는 함수들을 보면 대부분 객체가 닿을 수 없는 상태가 되면 이들에 대한 참조도 닿을 수 없도록 해주고 있습니다. 하지만 이런 코드들도 종종 볼 수 있습니다. var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); // 매 5초 마다 실행 위 코드는 참조 노드나 데이터가 더 이상 필요로하지 않는 타이머를 사용한 결과를 보여줍니다. renderer객체는 어느 시점에 다른 것으로 대체되거나 제거될 수 있으며 그러면 인터벌 핸들러로 둘러쌓은 코드는 더 이상 필요 없게 됩니다. 이 인터벌 타이머는 아직 활성 상태이므로 가비지컬렉터는 이 핸들러나 그 내부의 것들을 가져가지 않습니다. 결국은 많은 양의 데이터를 저장하고 처리하고 있을 serverData도 가져가지 않게되는 것입니다. 옵저버를 사용할 때는 그 사용이 종료되었을 때 꼭 명시적으로 그것을 제거해야 합니다(해당 옵저버가 더 이상 필요하지 않거나 닿을 수 없는 상태일 때). 운이 좋게도 대부분의 현대적인 브라우저에서는 이러한 일을 대신 해줍니다. 이들은 개발자가 리스너를 제거하는 것을 잊었다고 하더라도 객체가 닿을 수 없는 상태가 되면 자동으로 옵저버 핸들러를 가져갑니다. 예전에는 이러한 경우에 대처를 하지 못했습니다(IE6 같은 경우). 하지만 그럼에도 불구하고 객체의 사용이 끝나면 그 옵저버는 제거하는 것이 모범사례입니다. 다음 예를 봅시다. var element = document.getElementById('launch-button'); var counter = 0; function onClick(event) { counter++; element.innerHtml = 'text ' + counter; } element.addEventListener('click', onClick); // 필요한 작업 수행 element.removeEventListener('click', onClick); element.parentNode.removeChild(element); // 이제 요소들이 스코프를 벗어나게 되면 // 순환 참조를 잘 처리하지 못 하는 구형 웹브라우저에서도 // 해당 요소와 onClick 콜백을 가비지컬렉터가 가져감 더 이상 노드를 닿을 수 없는 상태로 만들기 전에 removeEventListener를 호출할 필요가 없습니다. 왜냐하면 현대적 브라우저들은 이러한 순환참조를 탐지하고 적절히 처리하는 가비지 컬렉터를 지원하기 때문입니다. 만약 jQuery를(혹은 이런 기능을 지원하는 다른 라이브러리도 존재함) 사용한다면 노드가 사용 불가 상태가 되기 전에 리스너를 제거할 수 있습니다. jQuery는 프로그램이 구형 브라우저에서 동작할 때도 메모리 누수가 없도록 처리해줍니다.
3: 클로져
자바스크립트 개발의 주요한 점 중 하나는 클로져입니다. 클로져는 자신을 감싸고 있는 바깥 함수의 변수에 접근할 수 있는 내부의 함수를 말합니다. 자바스크립트 런타임 구현의 특성상 다음과 같이 메모리 누수를 일으키는 것이 가능합니다. var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) // 'originalThing'에 대한 참조 console.log("hi"); }; theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log("message"); } }; }; setInterval(replaceThing, 1000); 일단 replaceThing이 호출되면 theThing은 커다란 배열과 새로운 클로져(someMethod)를 포함하는 새로운 객체를 얻게 됩니다. 아직 originalThing은 unused 변수가 갖고 있는 클로져에 의해 참조되고 있습니다(which is theThing variable from the previous call to replaceThing). 기억할 점은 한 번 동일한 부모 스코프에 있는 클로져들에 대한 스코프가 생성되고 나면 이것은 공유된다는 점입니다. 위의 경우 someMethod 클로져를 위해 생성된 스코프는 unused와 공유되었습니다. unused는 originalThing에 대한 참조를 갖고 있습니다. unused가 다시 사용되지 않는다 해도 someMethod는 theThing을 통해 replaceThing의 스코프 바깥에서 사용될 수 있습니다(글로벌하게). 그리고 someMethod는 unused와 클로져 스코프를 공유하기 때문에 unused가 originalThing에 대해 갖고 있는 참조 때문에 강제로 활성 상태가 유지됩니다(두 클로져 사이에 공유된 전체 스코프). 이 때문에 가비지 컬렉션이 작동하지 않습니다. 위의 예에서 someMethod 클로져에 대해 생성된 스코프는 unused와 공유되었고 또한 unused는 originalThing을 참조합니다. unused가 사용된 적이 없다고 하더라 someMethod는 replaceThing 스코프의 바깥에서 theThing을 통해 사용될 수 있습니다. unused가 originalThing을 참조한다는 사실 때문에 unused는 활성 상태가 유지되는데 왜냐하면 someMethod가 unused와 클로져 스코프를 공유하기 때문입니다. 이러한 점들은 모두 꽤 큰 메모리 누수를 일으킬 수 있습니다. 위 코드가 실행될 때마다 메모리 사용량이 갑자기 증가하는 것을 볼 수 있을 것입니다. 가비지 컬렉터가 수행되어도 그 크기는 줄어들지 않을 것입니다. 클로져들 사이의 연결된 리스트가 한번 생성되면 (이번 예시의 경우에는 그 루트가 theThing 변수가 됨) 각각의 클로져 스코프는 커다란 배열에 대한 간접적 참조를 전달합니다.
4: DOM에서 벗어난 요소 참조
DOM 노드를 데이터 구조 속에 저장하는 경우가 있습니다. 테이블 내 몇 열의 내용을 빠르게 업데이트하고 싶은 상황이라고 가정해 봅시다. 각 열에 대한 참조를 딕셔너리나 배열에 저장하면 동일한 DOM 요소에 대해 두 개의 참조가 존재하는 셈입니다. 하나는 DOM 트리에, 하나는 딕셔너리에. 이 열들을 제거하고자 결정한다면 이 두 개의 참조 모두가 닿을 수 없도록 해야 하는 것을 잊지 말아야 합니다. var elements = { button: document.getElementById('button'), image: document.getElementById('image') }; function doStuff() { elements.image.src = 'http://example.com/image_name.png'; } function removeImage() { // image는 body 요소의 바로 아래 자식임 document.body.removeChild(document.getElementById('image')); // 이 순간까지 #button 전역 요소 객체에 대한 참조가 아직 존재함 // 즉, button 요소는 아직도 메모리 상에 있고 가비지 컬렉터가 가져갈 수 없음 } DOM 트리의 리프 노드나 내부 노드를 참조할 때 고려해야 할 것이 또 있습니다. 테이블 내의 셀 태그 (예를 들어)를 참조하고 있다가 해당 테이블을 DOM에서 제거한 상태에서 해당 셀에 대한 참조를 갖고 있다면 커다란 메모리누수가 일어날 수 있습니다. 해당 셀만 놔두고 나머지 부분을 가비지컬렉터가 반환시켜줄 거라고 생각할지도 모르겠습니다만 실제로는 그렇지 않습니다. 그 셀은 테이블의 자식 노드이고 자식 노드들은 부모에 대한 참조를 갖고 있기 때문에 테이블 셀에 대한 참조 하나만으로도 전체 테이블이 메모리에 남아 있게 됩니다. 세션 스택의 개발자들은 메모리 할당을 적절히 처리할 수 있도록 코드를 작성하는 모범 사례를 따르고 있습니다. 일단 사용자의 프로덕션 웹 앱이 세션 스택에 통합되면 세션 스택은 모든 것을 기록합니다. 모든 DOM 변화와 사용자 상호작용, 자바스크립트 에러, 스택트 레이스, 실패한 네트워크 요청, 디버깅 메시지 등. 세션 스택을 통해 웹앱의 문제를 비디오처럼 재생해 볼 수 있으며 사용자가 보는 화면과 동일한 화면을 볼 수 있습니다. 그런데 이 모든 것은 사용자의 웹 앱에 속도 저하 없이 이루어져야 합니다. 사용자들은 페이지를 리로딩하거나 앱 내부를 돌아다닐 수 있기 때문에 모든 옵저버, 인터셉터, 변수 할당 등이 적절히 처리되어야 합니다. 그래야 어떠한 메모리 누수도 일어나지 않고 우리가 통합하는 앱의 메모리 소비가 증가하지 않기 때문입니다.