>웹 프론트엔드 >JS 튜토리얼 >자바스크립트 가비지 수집 메커니즘 및 메모리 누수_javascript 기술에 대한 자세한 분석

자바스크립트 가비지 수집 메커니즘 및 메모리 누수_javascript 기술에 대한 자세한 분석

WBOY
WBOY원래의
2016-05-16 17:16:27949검색

Javascript에는 자동 가비지 수집 메커니즘이 있습니다. 즉, 실행 환경이 코드 실행 중에 사용되는 메모리를 관리해야 한다는 의미입니다. C 및 C++와 같은 언어에서 개발자의 기본 작업은 많은 문제의 원인이 되는 메모리 사용량을 수동으로 추적하는 것입니다. JavaScript 프로그램을 작성할 때 개발자는 더 이상 메모리 사용량에 대해 걱정할 필요가 없습니다. 필요한 메모리 할당과 불필요한 재활용이 완전히 자동화되었습니다. 이 가비지 수집 메커니즘의 원리는 실제로 매우 간단합니다. 더 이상 사용되지 않는 변수를 찾은 다음 해당 변수가 차지한 메모리를 해제합니다. 이를 위해 가비지 컬렉터는 고정된 시간 간격(또는 코드 실행에 미리 설정된 수집 시간)에 따라 주기적으로 이 작업을 수행합니다.

함수에서 지역 변수의 일반적인 수명주기를 분석해 보겠습니다. 지역 변수는 함수 실행 중에만 존재합니다. 이 과정에서 해당 값을 저장하기 위해 로컬 변수에 대한 스택(또는 힙) 메모리에 해당 공간이 할당됩니다. 그런 다음 이러한 변수는 함수 실행이 끝날 때까지 함수에서 사용됩니다. 이 시점에서는 지역 변수가 더 이상 존재할 필요가 없으므로 나중에 사용할 수 있도록 해당 메모리를 해제할 수 있습니다. 이 경우 변수가 여전히 필요한지 여부를 판단하는 것은 쉽지만 모든 경우에 그렇게 쉽게 결론을 내릴 수는 없습니다. 가비지 수집기는 유용한 변수와 그렇지 않은 변수를 추적하고 더 이상 유용하지 않은 변수를 표시하여 나중에 해당 변수가 차지하는 메모리를 회수할 수 있도록 해야 합니다. 쓸모없는 변수를 식별하는 데 사용되는 전략은 구현에 따라 다를 수 있지만 브라우저에서의 구현에 따라 일반적으로 두 가지 전략이 있습니다.

마크 클리어

JavaScript에서 가장 일반적으로 사용되는 가비지 수집 방법은 표시 및 청소입니다. 변수가 환경에 들어가면(예: 함수에서 변수를 선언하는 경우) 해당 변수는 "환경에 들어감"으로 표시됩니다. 논리적으로 환경에 진입하는 변수가 차지하는 메모리는 실행 흐름이 해당 환경에 진입할 때마다 사용될 수 있으므로 결코 해제될 수 없습니다. 그리고 변수가 환경을 떠날 때 이는 "환경을 떠나는 것"으로 표시됩니다.

변수는 어떤 방식으로든 표시될 수 있습니다. 예를 들어 변수가 환경에 들어갈 때 특수 비트를 뒤집어 기록하거나 "환경에 들어가는" 변수 목록과 "환경에서 나가는" 변수 목록을 사용하여 어떤 변수가 변경되었는지 추적할 수 있습니다. 결국 변수를 어떻게 표시하느냐가 아니라 어떤 전략을 채택하느냐가 중요합니다.

가비지 수집기가 실행되면 메모리에 저장된 모든 변수를 표시합니다(물론 모든 표시 방법을 사용할 수 있습니다). 그런 다음 환경의 변수와 환경의 변수가 참조하는 변수 태그를 제거합니다. 이후에 표시된 변수는 환경 내의 변수가 더 이상 해당 변수에 접근할 수 없으므로 삭제할 변수로 간주됩니다. 마지막으로 가비지 수집기는 메모리 정리 작업을 완료하여 표시된 값을 삭제하고 해당 값이 차지하는 메모리 공간을 회수합니다.

2008년 현재 IE, Firefox, Opera, Chrome 및 Safari의 JavaScript 구현은 모두 표시 및 청소 가비지 수집 전략(또는 유사한 전략)을 사용하지만 가비지 수집 간격은 다릅니다.

참조 횟수

덜 일반적인 또 다른 가비지 수집 전략은 참조 계산입니다. 참조 카운팅의 의미는 각 값이 참조되는 횟수를 추적하는 것입니다. 변수를 선언하고 참조 유형 값을 변수에 할당하면 이 값에 대한 참조 수는 1입니다. 동일한 값이 다른 변수에 할당되면 해당 값의 참조 카운트가 1 증가합니다. 반대로, 이 값에 대한 참조가 포함된 변수가 다른 값을 얻게 되면 이 값에 대한 참조 개수가 1개 감소합니다. 이 값에 대한 참조 개수가 0이 되면 이 값에 접근할 수 있는 방법이 없다는 의미입니다. 값이므로 가능합니다. 점유된 메모리 공간을 회수합니다. 그런 다음 다음에 가비지 수집기가 실행될 때 참조가 0인 값이 차지한 메모리를 해제합니다.

Netscape Navigator 3.0은 참조 계산 전략을 사용한 최초의 브라우저였지만 곧 순환 참조라는 심각한 문제에 직면했습니다. 순환 참조는 객체 A가 객체 B에 대한 참조를 포함하고 객체 B도 객체 A에 대한 참조를 포함함을 의미합니다.

다음 예를 참조하세요.

코드 복사 코드는 다음과 같습니다. :

함수() {
var objectA = new Object();
var objectB = new Object();

objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}

이 예에서 objectA와 objectB는 각각의 속성을 통해 서로를 참조합니다. 즉, 두 개체의 참조 수는 2입니다. 인덱스 지우기를 사용하는 구현에서는 함수가 실행된 후 두 개체 모두 범위를 벗어납니다. 따라서 두 가지가 서로 참조하는 것은 문제가 되지 않습니다. 그러나 참조 계산 전략을 사용하는 구현에서는 함수가 실행된 후에도 objectA 및 objectB가 계속 존재하므로 참조 수가 0이 되지 않습니다. 이 함수를 반복적으로 호출하면 많은 양의 메모리가 재활용되지 않습니다. 따라서 Netscape는 Navigator 4.0에서 참조 카운터 방식을 포기하고 대신 마크 지우기를 사용하여 가비지 수집 메커니즘을 구현했습니다. 그러나 참조 카운팅으로 인한 문제는 여기서 끝나지 않습니다.

우리는 IE의 일부 개체가 기본 자바스크립트 개체가 아니라는 것을 알고 있습니다. 예를 들어 BOM과 DOM의 객체는 C를 사용하여 COM(Component Object Model) 객체 형태로 구현되며, COM 객체의 가비지 수집 메커니즘은 참조 계산 전략을 사용합니다. 따라서 IE의 JavaScript 엔진이 표시 및 지우기 전략을 사용하여 구현되었더라도 JavaScript에서 액세스하는 COM 개체는 여전히 참조 계산 전략을 기반으로 합니다. 즉, COM 개체가 IE에서 설계되는 한 순환 참조 문제가 발생합니다.

다음의 간단한 예는 COM 개체 사용으로 인해 발생하는 순환 참조 문제를 보여줍니다.

코드 복사 코드는 다음과 같습니다.

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element .somObject = myObject;

이 예제에서는 DOM 요소(element)와 기본 JavaScript 개체(myObject) 사이에 순환 참조를 만듭니다. 그중 myObject 변수에는 요소 개체를 가리키는 element라는 속성이 있고, element 변수에는 myObject를 다시 가리키는 someObject라는 속성도 있습니다. 이 순환 참조로 인해 예제의 DOM이 페이지에서 제거되더라도 재활용되지 않습니다.

이와 같은 순환 참조 문제를 방지하려면 사용되지 않는 기본 JavaScript 개체를 DOM 요소에서 수동으로 연결 해제하는 것이 가장 좋습니다. 예를 들어 다음 코드를 사용하여 이전 예제에서 생성된 순환 참조를 제거할 수 있습니다.

코드 복사 코드는 다음과 같습니다. 다음과 같습니다:

myObject.element = null;
element.somObject = null;

변수를 null로 설정한다는 것은 변수와 이전에 참조한 값 사이의 연결이 끊어진다는 의미입니다. 그러나 다음에 가비지 수집기가 실행되면 이러한 값은 삭제되고 해당 값이 차지하는 메모리는 회수됩니다.

성능 문제

가비지 컬렉터는 주기적으로 실행되며, 변수에 할당된 메모리 양이 객관적이라면 재활용 작업량도 상당히 큽니다. 이 경우 가비지 수집 간격을 결정하는 것은 매우 중요한 문제입니다. 가비지 수집기가 실행되는 빈도와 관련하여 IE의 악명 높은 성능 문제를 생각하지 않는 것은 어렵습니다. IE의 가비지 수집기는 메모리 할당, 특히 256개의 변수, 4096개의 개체(또는 배열) 리터럴 및 배열 요소(슬롯) 또는 64KB 문자열을 기반으로 실행됩니다. 위의 임계값 중 하나라도 도달하면 가비지 수집기가 실행됩니다. 이 구현의 문제점은 스크립트에 그렇게 많은 변수가 포함되어 있으면 스크립트가 전체 수명 동안 그만큼 많은 변수를 유지할 가능성이 있다는 것입니다. 결과적으로 가비지 수집기가 자주 실행되어야 할 수 있습니다. 결과적으로 가비지 수집 루틴의 초기 IE7 재작성으로 인해 심각한 성능 문제가 발생했습니다.

IE7이 출시되면서 JavaScript 엔진의 가비지 수집 루틴이 작동 방식이 변경되었습니다. 즉, 가비지 수집을 트리거하는 변수 할당, 리터럴 및/또는 배열 요소의 중요한 값이 동적 수정에 맞게 조정되었습니다. IE7의 임계값은 초기화 시 IE6의 임계값과 동일합니다. 루틴이 할당된 메모리의 15% 미만을 회수하는 경우 변수, 리터럴 및/또는 배열 요소에 대한 임계값이 두 배가 됩니다. 루틴이 할당된 메모리의 85%를 회수하면 다양한 임계값이 기본값으로 재설정됩니다. 간단해 보이는 이 조정은 JavaScript가 많이 포함된 페이지를 실행할 때 IE의 성능을 크게 향상시킵니다.

실제로 일부 브라우저에서는 가비지 수집 프로세스가 실행될 수 있지만 독자에게는 이를 권장하지 않습니다. IE에서는 window.CollectGarbage() 메서드를 호출하면 즉시 가비지 수집을 가리킵니다. Opera7 이상에서는 window.opera.collect()를 호출하면 가비지 수집 루틴도 시작됩니다.

메모리 관리

가비지 수집 메커니즘이 있는 언어로 프로그램을 작성하면 개발자는 일반적으로 메모리 관리 문제에 대해 걱정할 필요가 없습니다. 그러나 메모리 관리 및 가비지 수집 분야에서 JavaScript가 직면한 문제는 여전히 약간 다릅니다. 가장 중요한 문제 중 하나는 웹 브라우저에 할당된 사용 가능한 메모리 양이 일반적으로 데스크톱 애플리케이션에 할당된 메모리 양보다 적다는 것입니다. 이것의 목적은 주로 보안상의 이유이며, Javascript를 실행하는 웹 페이지가 모든 시스템 메모리를 소모하여 시스템 충돌을 일으키는 것을 방지하는 것이 목적입니다. 메모리 제한 문제는 변수에 대한 메모리 할당뿐만 아니라 호출 스택 및 스레드에서 동시에 실행될 수 있는 명령문 수에도 영향을 미칩니다.

따라서 페이지가 최소한의 메모리를 차지하도록 하면 페이지 성능이 향상될 수 있으며 해당 값을 null로 설정하여 참조를 해제하는 것이 가장 좋습니다. 이 방법을 역참조라고 합니다. 이 접근 방식은 대부분의 전역 변수 및 전역 개체의 속성에 사용됩니다. 지역 변수는 다음 예와 같이 환경을 실행할 때 자동으로 역참조됩니다.

코드 복사 코드는 다음과 같습니다.

function createPerson(이름) {
var localPerson = new Object();
localPerson.name = name;
return localPerson;
};
var gllbalPerson = createPerson("니콜라스");

// globalPerson 수동 역참조
globalPerson = null;


이 예에서 globalPerson 변수는 createPerson() 함수에서 반환된 값을 가져옵니다. createPerson() 함수 내에서 객체를 생성하고 이를 로컬 변수 localPerson에 할당한 다음 name이라는 속성을 객체에 추가합니다. 마지막으로 이 함수가 호출되면 localPerson이 함수로 반환되고 전역 변수 globalPerson에 할당됩니다. localPerson은 createPerson() 함수가 실행된 후 실행 환경을 떠나기 때문에 명시적으로 역참조할 필요가 없습니다. 그러나 전역 변수 globalPerson의 경우 이를 사용하지 않을 때 수동으로 역참조해야 합니다. 이것이 위 예제의 마지막 코드 줄의 목적입니다.

그러나 값을 역참조한다고 해서 해당 값이 차지하는 메모리가 자동으로 재활용되는 것은 아닙니다. 역참조가 실제로 수행하는 작업은 가비지 수집기가 다음에 실행될 때 해당 값을 회수할 수 있도록 실행 환경에서 값을 가져오는 것입니다.

메모리 누수

IE는 JScript 개체와 COM 개체에 대해 서로 다른 가비지 수집 루틴을 사용하기 때문에 클로저로 인해 IE에서 몇 가지 특별한 문제가 발생합니다. 특히 HTML 요소가 클로저의 범위 체인에 저장되어 있으면 해당 요소가 파괴될 수 없음을 의미합니다. 다음 예를 살펴보겠습니다.

코드 복사 코드는 다음과 같습니다.

function 할당Handler() {
var element = document.getElementById("someElement");
element.onclick = function() {
Alert(element.id);
};
};

위 코드는 요소 시간 핸들러로 클로저를 생성하고 이 클로저는 순환 참조를 생성합니다. 익명 함수는 AssignHandler()의 활성 객체에 대한 참조를 저장하므로 요소에 대한 참조 수를 줄일 수 없습니다. 익명 함수가 존재하는 한 요소의 참조 번호는 최소 1이므로 차지하는 메모리는 결코 재활용되지 않습니다. 하지만 이 문제는 다음과 같이 코드를 살짝 다시 작성하면 해결될 수 있습니다.
코드 복사 코드는 다음과 같습니다.

function 할당Handler() {
var element = document.getElementById("someElement");
var id = element.id;

element.onclick = function() {
            Alert(id);
   };

     요소 = null; 클로저에서 해당 변수를 참조하면 순환 참조가 제거됩니다. 하지만 이 단계만으로는 메모리 누수 문제를 해결할 수 없습니다. 클로저는 요소를 포함하는 함수 활동을 포함하는 전체 활동 개체를 참조한다는 점을 기억해야 합니다. 클로저가 요소를 직접 참조하지 않더라도 참조는 함수를 포함하는 활성 개체에 계속 저장됩니다. 따라서 요소 변수를 null로 설정해야 합니다. 이러한 방식으로 DOM 개체에 대한 참조를 해제할 수 있고, 참조 수를 성공적으로 줄일 수 있으며, DOM 개체가 차지하는 메모리를 적절하게 재활용할 수 있습니다.


설명

1. 해당 창에 있는 개체의 참조를 다른 창에 보관하면 창을 닫아도 메모리가 해제되지 않습니다.

2. 더 나쁜 점은 DOM 개체에 대한 참조를 유지하고 해당 개체가 있는 창을 닫으면 IE가 충돌하고 메모리 오류를 보고하거나 다시 시작해야 한다는 것입니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.