IE의 Memory Leaks

이 문서는 원본글 작성자인 Douglas Crockford 씨의 동의를 얻어 번역한 글입니다.

_____________________________________________________________________
시스템이 메모리 관리를 올바르게 하지 못한다면 그것을 메모리 릭이라 부른다. 메모리 릭은 일종의 버그이다. 증상으로는 퍼포먼스를 저하시키거나 오류가 발생하는 것 등이 있다.

Microsoft 사의 인터넷 익스플로러에는 많은 종류의 메모리 릭이 내재하고 있는데, 그 중에서 가장 악질은 JScript 와 연동할 때 발생하는 것이다. DOM 객체가 JavaScript 객체(이벤트 핸들링 함수같은)의 레퍼런스를 포함하고, JavaScript 객체가 해당 DOM 객체에 대한 레퍼런스를 포함할 때, closure가 형성된다. 이것 자체로는 문제가 되지 않는다. DOM 객체와 이벤트 핸들러에 대한 다른 참조가 없을 때, 가비지 컬렉터garbage collector (자동 리소스 관리자)는 그들을 교정하고 사용되었던 메모리 공간을 재할당이 가능하도록 한다. JavaScript의 가비지 컬렉터는 closure에 대해 잘 이해하기 때문에 그것으로 인한 혼란이 없다. 하지만, 안타깝게도 IE의 DOM은 JScript가 관리한다. JScript의 메모리 관리자는 closure에 대해 잘 이해하지 못하기 때문에 매우 많은 혼란을 겪는다. 그 결과, closure가 발생할 때, 메모리 교정이 일어나지 않으며, 교정되지 않은 메모리를 가리켜 누수되었다leaked고 한다. 시간이 흐르면, 이런 누수현상이 메모리 자원부족memory starvation을 유발한다. 사용된 셀이 메모리를 가득 채우면 브라우저는 자원부족으로 죽어버릴 것이다.

위에서 언급한 현상을 시연해 볼 수 있다. 첫번째 프로그램인 queuetest1에서 10000개의 DOM 엘리먼트(span)를 만들고 동시에 최근 10개를 제외한 나머지를 전부 삭제할 것이다. 프로그램을 실행할 때, 윈도우즈의 작업관리자의 성능탭을 이용하면 페이지 파일(PF = Page File) 사용량이 항상 일정한지 볼 수 있다. PF 사용량이 변한다면 이는 메모리 할당이 비효율적이라는 것을 의미한다.
다음으로 두번째 프로그램인 queuetest2를 실행해보자. queuetest1과 똑같은 동작을 하지만 각 엘리먼트에 click 핸들러를 추가했다. 모질라와 오페라에서는 PF 사용량이 거의 일정한 반면에 IE에서는 메모리 누수로 초당 1MB 정도의 비율로 사용량이 점점 증가하는 것을 알 수 있다. 종종 이러한 누수현상은 모른채 지나쳤다. 하지만 Ajax 기술이 더 유명해짐에 따라 각 페이지에서 더 오래 머무르게 되고 더 많은 변화와 실패가 일반적이게 되었다.

IE가 자기일이나 closure 교정을 제대로 못하기 때문에 그 일은 고스란히 우리 몫이 되었다. 명시적으로 closure를 해제한다면 IE도 메모리를 교정할 수 있다. Microsoft에 따르면, closures가 메모리 누수의 원인이 된다고 한다. 이건 물론 매우 틀린 말이지만, Microsoft가 자신들의 버그에 대해서 개발자들에게 아주 나쁜 조언을 하는 결과를 가져왔다. closure는 DOM 쪽에서 끊는 것이 더 쉽다. 사실상 JScript 쪽에서 closure를 끊기란 불가능하다.

엘리먼트를 다룰 때는, closure를 끊기 전에 반드시 모든 이벤트 핸들러를 null로 만들어야 한다. 모든 이벤트 핸들러 프로퍼티에 null값을 할당하기만 하면 된다. 이런 기능을 필요할 때마다 혹은 purge와 같은 일반적인 함수로 만들어 실행할 수도 있다.

purge 함수는 DOM 엘리먼트를 인자로 받는다. 엘리먼트의 어트리뷰트를 루프돌면서, 함수를 찾으면 null 로 만들어 순환구조를 끊고 메모리가 교정될 수 있도록 한다. 함수는 또한 모든 하위 엘리먼트들에도 같은 작용을 해서 closure가 잘 제거되도록 한다. purge 함수는 모질라와 오페라에는 아무런 해가 없으며 IE에만 작용한다. purge 함수는 엘리먼트가 removeChild 메소드나 innerHTML 속성으로 삭제되기 전에 호출되어야 한다.

function purge(d) {
	var a = d.attributes, i, l, n;

	if (a) {
		l = a.length;
		for (i = 0; i < l; i += 1) {
			n = a[i].name;

			if (typeof d[n] === 'function') {
				d[n] = null;
			}
		}
	}

	a = d.childNodes;

	if (a) {
		l = a.length;

		for (i = 0; i < l; i += 1) {
			purge(d.childNodes[i]);
		}
	}
}

끝으로 세번째 프로그램 queuetest3를 실행해보자. 여기서는 DOM 엘리먼트를 삭제하기 전에 purge 함수를 호출했다.

댓글을 남겨주세요