Chrome으로 자바 스크립트 메모리 누수 찾기


163

백본 뷰를 만들고 처리기를 이벤트에 연결하고 사용자 정의 클래스를 인스턴스화하는 매우 간단한 테스트 사례를 만들었습니다. 이 샘플에서 "제거"버튼을 클릭하면 모든 것이 정리되고 메모리 누수가 없어야합니다.

코드에 대한 jsfiddle은 다음과 같습니다. http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

그러나 Chrome 프로필러를 사용하여 이것이 사실인지 확인하는 방법을 잘 모르겠습니다. 힙 프로파일 러 스냅 샷에 표시되는 내용이 많으며, 무엇이 좋은지 나쁜지를 해독하는 방법을 모릅니다. 지금까지 살펴본 튜토리얼은 "스냅 샷 프로파일 러 사용"을 지시하거나 전체 프로파일 러의 작동 방식에 대해 매우 자세하게 설명합니다. 프로파일 러를 도구로 사용하는 것이 가능합니까? 아니면 전체가 어떻게 설계되었는지 이해해야합니까?

편집 : 다음과 같은 자습서 :

Gmail 메모리 누수 수정

DevTools 사용

내가 본 것 중에서 더 강력한 자료를 대표합니다. 그러나 3 Snapshot Technique 의 개념을 소개하는 것 외에도 실용적인 지식 (나 같은 초보자에게)에 대해서는 거의 제공하지 않습니다. 'DevTools 사용하기'튜토리얼은 실제 예제를 통해 작동하지 않으므로, 모호하고 일반적인 개념적인 개념 설명은 그다지 도움이되지 않습니다. 'Gmail'예는 다음과 같습니다.

누수가 발견되었습니다. 이제 뭐?

  • 프로파일 패널의 아래쪽 절반에서 누출 된 객체의 유지 경로를 검사합니다.

  • 할당 사이트를 쉽게 유추 할 수없는 경우 (예 : 이벤트 리스너) :

  • JS 콘솔을 통해 보유 객체의 생성자를 계측하여 할당을위한 스택 추적을 저장합니다.

  • 클로저를 사용하십니까? 적절한 기존 플래그 (예 : goog.events.Listener.ENABLE_MONITORING)를 활성화하여 생성 중에 creationStack 속성을 설정하십시오

나는 그것을 읽은 후에 더 혼란스러워합니다. 그리고 다시, 그냥 말해 것은 할 수 할 수 없는 상황을 어떻게 그들이 할 수 있습니다. 내 관점에서 볼 때 모든 정보는 모호하거나 이미 프로세스를 이해 한 사람에게만 의미가 있습니다.

이보다 구체적인 문제 중 일부는 아래 @Jonathan Naguin의 답변 에서 제기되었습니다 .


2
브라우저에서의 메모리 사용 테스트에 대해서는 아무것도 모르지만, 보지 못한 경우 Chrome 웹 검사기에 대한 Addy Osmani의 기사 가 도움이 될 수 있습니다.
Paul D. Waite

1
제안 해 주셔서 감사합니다, 폴 그러나 제거를 클릭하기 전에 하나의 스냅 샷을 찍은 다음 클릭 한 후 다른 스냅 샷을 찍은 다음 '스냅 샷 1과 2 사이에 할당 된 객체'를 선택하면 (자기 기사에서 제안한대로) 여전히 2000 개가 넘는 객체가 있습니다. 예를 들어 4 개의 'HTMLButtonElement'항목이 있습니다. 진실로, 나는 무슨 일이 일어나고 있는지 전혀 모른다.
EleventyOne

3
별로 도움이되지 않는 것 같습니다. JavaScript와 같은 가비지 수집 언어를 사용하면 실제로 테스트와 같은 수준에서 메모리로 수행중인 작업을 확인하려는 것이 아닙니다. 메모리 누수를 확인하는 더 좋은 방법 main은 한 번이 아니라 10,000 번 을 호출 하고 결국에는 더 많은 메모리가 사용되는지 확인하는 것입니다.
Paul D. Waite

3
@ PaulD.Waite 그래, 아마도. 그러나 "문제는 어딘가에있다"고 말할 수있는 것이 아니라 문제가 무엇인지 정확하게 판단하기 위해서는 여전히 세부적인 수준의 분석이 필요한 것 같습니다. 그리고 그런 세밀한 수준에서 프로파일 러를 사용할 수 있어야한다는 인상을받습니다 ... 어떻게
해야할지 모르겠습니다

답변:


205

메모리 누수를 찾는 좋은 워크 플로는 Loreena Lee와 Gmail 팀이 일부 메모리 문제를 해결하기 위해 처음 사용 하는 세 가지 스냅 샷 기술입니다. 단계는 일반적으로 다음과 같습니다.

  • 힙 스냅 샷을 만듭니다.
  • 물건을하십시오.
  • 다른 힙 스냅 샷을 작성하십시오.
  • 같은 것을 반복하십시오.
  • 다른 힙 스냅 샷을 작성하십시오.
  • 스냅 샷 3의 "요약"보기에서 스냅 샷 1과 2 사이에 할당 된 개체를 필터링하십시오.

귀하의 예를 들어, 시작 버튼의 클릭 이벤트가 발생할 때까지 백본 뷰 작성을 지연시키는 이 프로세스 ( 여기에서 찾을 수 있음)를 표시하도록 코드를 조정했습니다 . 지금:

  • 주소 를 사용하여 로컬에 저장된 HTML을 실행하고 스냅 샷을 만듭니다.
  • 시작을 클릭하여보기를 작성하십시오.
  • 다른 스냅 샷을 찍습니다.
  • 제거를 클릭하십시오.
  • 다른 스냅 샷을 찍습니다.
  • 스냅 샷 3의 "요약"보기에서 스냅 샷 1과 2 사이에 할당 된 개체를 필터링하십시오.

이제 메모리 누수를 찾을 준비가되었습니다!

몇 가지 다른 색상의 노드가 있습니다. 레드 노드는 Javascript에서 직접 참조하지 않지만 분리 된 DOM 트리의 일부이므로 활성 상태입니다. 트리에 Javascript에서 참조한 노드 (폐쇄 또는 변수 일 수 있음)가있을 수 있지만 우연히 전체 DOM 트리가 가비지 수집되지 못하게합니다.

여기에 이미지 설명을 입력하십시오

그러나 노란색 노드는 Javascript에서 직접 참조합니다. 동일한 분리 된 DOM 트리에서 노란색 노드를 찾아 Javascript에서 참조를 찾으십시오. DOM 창에서 요소로 연결되는 일련의 속성이 있어야합니다.

특히 HTML Div 요소가 빨간색으로 표시되어 있습니다. 요소를 확장하면 "캐시"함수에 의해 참조되는 것을 볼 수 있습니다.

여기에 이미지 설명을 입력하십시오

행을 선택하면 콘솔 유형 $ 0에 실제 기능과 위치가 표시됩니다.

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

요소가 참조되는 곳입니다. 불행히도 할 수있는 일은 많지 않으며 jQuery의 내부 메커니즘입니다. 그러나 테스트 목적으로 함수를 이동하고 메소드를 다음과 같이 변경하십시오.

function cache( key, value ) {
    return value;
}

이제 프로세스를 반복하면 빨간색 노드가 표시되지 않습니다 :)

선적 서류 비치:


8
노력해 주셔서 감사합니다. 실제로, 3 가지 스냅 샷 기술은 튜토리얼에서 정기적으로 언급됩니다. 불행히도, 세부 사항은 종종 생략됩니다. 예를 들어, $0콘솔에 기능 이 도입되어 주셔서 감사합니다. 물론, 그것이 무엇을하고 있는지 또는 어떻게 사용했는지 알지 못합니다 (같은 일을 $1하는 동안 쓸모없는 $2것처럼 보입니다). 둘째, #button in function cache()다른 수십 개의 행이 아닌 행을 강조 표시하는 방법을 어떻게 알았 습니까? 마지막으로,이 빨간색 노드입니다 NodeListHTMLInputElement도,하지만 난 그들을 알아낼 수 없습니다.
EleventyOne

7
어떻게 아셨어요 cache다른 사람이하지 않았다 동안 행 정보를 포함? 거리보다 낮은 가지가 많이 있습니다 cache. 그리고 님이 HTMLInputElement님의 자녀 임을 어떻게 알았는지 모르겠습니다 HTMLDivElement. 내부에서 참조 된 것 ( "HTMLDivElement의 기본")을 볼 수도 있지만, 나 자신과 두 개의을 참조하기도합니다 HTMLButtonElement. 이 예제에 대한 답을 찾아 주셔서 감사하지만이 문제를 다른 문제로 일반화하는 방법을 모르겠습니다.
EleventyOne

2
이상하게도, 나는 당신의 예를 사용하고 있었고 당신과 다른 결과를 얻었습니다 (스크린 샷에서). 그럼에도 불구하고, 나는 당신의 모든 도움에 크게 감사드립니다. 지금은 충분하다고 생각하고 구체적인 도움이 필요한 실제 사례가 있으면 여기에 새로운 질문을 작성합니다. 다시 감사합니다.
EleventyOne

2
$ 0에 대한 설명은 여기에서 찾을 수 있습니다. developer.chrome.com/devtools/docs/commandline-api#0-4
Sukrit Gupta

4
무슨 Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.뜻입니까?
K-SO의 독성이 증가하고 있습니다.

8

다음은 jsfiddle의 메모리 프로파일 링에 대한 팁입니다. 다음 URL을 사용하여 jsfiddle 결과를 분리하면 모든 jsfiddle 프레임 워크가 제거되고 결과 만로드됩니다.

http://jsfiddle.net/4QhR2/show/

다음 문서를 읽을 때까지 타임 라인과 프로파일 러를 사용하여 메모리 누수를 추적하는 방법을 알 수 없었습니다. '객체 할당 추적기'라는 제목의 섹션을 읽은 후 '레코드 힙 할당'도구를 사용하고 일부 분리 된 DOM 노드를 추적 할 수있었습니다.

jQuery 이벤트 바인딩에서 Backbone 이벤트 위임 사용으로 전환하여 문제를 해결했습니다. 전화를 걸면 최신 버전의 Backbone이 자동으로 이벤트를 바인딩 해제한다는 것을 이해합니다 View.remove(). 데모 중 일부를 직접 실행하면 식별 할 수있는 메모리 누수로 설정됩니다. 이 문서를 공부 한 후에도 여전히 질문이 없으면 여기에 질문하십시오.

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling


6

기본적으로 힙 스냅 샷 내부의 개체 수를 확인해야합니다. 두 스냅 샷 사이에 객체 수가 증가하고 객체를 폐기 한 경우 메모리 누수가 발생합니다. 내 조언은 코드에서 분리되지 않은 이벤트 핸들러를 찾는 것입니다.


3
예를 들어 jsfiddle의 힙 스냅 샷을 보면 '제거'를 클릭하기 전에 100,000 개가 넘는 개체가 있습니다. 내 jsfiddle의 코드가 실제로 만든 객체를 어디에서 찾을 수 있습니까? 나는 Window/http://jsfiddle.net/4QhR2/show그것이 유용 할 것이라고 생각 했지만 그것은 끝없는 기능 일뿐입니다. 나는 거기에서 무슨 일이 일어나고 있는지 전혀 모른다.
EleventyOne

@ EleventyOne : jsFiddle을 사용하지 않습니다. 테스트를 위해 자신의 컴퓨터에 파일을 만드는 것이 어떻습니까?
푸른 하늘

1
@BlueSkies 나는 사람들이 동일한 코드베이스에서 작업 할 수 있도록 jsfiddle을 만들었습니다. 그럼에도 불구하고 테스트를 위해 내 컴퓨터에 파일을 만들 때 힙 스냅 샷에는 여전히 50,000 개 이상의 개체가 있습니다.
EleventyOne

@EleventyOne 하나의 힙 스냅 샷으로 메모리 누수가 있는지 여부를 알 수 없습니다. 최소한 두 개가 필요합니다.
Konstantin Dinev

2
과연. 나는 수천 개의 물체가 존재할 때 무엇을 찾아야 하는지를 아는 것이 얼마나 어려운지를 강조하고있었습니다.
EleventyOne


3

개발자 도구에서 타임 라인 탭을 볼 수도 있습니다. 앱 사용을 기록하고 DOM 노드 및 이벤트 리스너 수를 주시하십시오.

메모리 그래프가 실제로 메모리 누수를 나타내는 경우, 프로파일 러를 사용하여 누수를 파악할 수 있습니다.



2

나는 힙 스냅 샷을 찍기위한 조언을 두 번째로, 메모리 누수를 탐지하는 데 탁월하며 크롬은 탁월한 스냅 샷 작업을 수행합니다.

내 학위 연구 프로젝트에서 '계층'에 구축 된 많은 데이터를 생성 해야하는 대화 형 웹 응용 프로그램을 작성 중이었습니다.이 계층 중 많은 계층이 UI에서 '삭제'되지만 어떤 이유로 메모리가 부족하지 않았습니다. 스냅 샷 도구를 사용하여 할당 취소하고 JQuery가 객체에 대한 참조를 유지하고 있음을 확인할 수있었습니다 (소스는 .load() 범위를 벗어나더라도 참조를 유지 이벤트 ). 이 정보를 한 손으로 내 프로젝트에 저장하면 다른 사람들의 라이브러리를 사용할 때 GC가 작업을 수행하지 못하게하는 참조가 남아있는이 문제가 있습니다.

편집 : 스냅 샷 작성 시간을 최소화하기 위해 수행 할 작업을 미리 계획하고 문제를 일으킬 수있는 원인을 가정하고 각 시나리오를 테스트하여 전후에 스냅 샷을 만드는 것이 유용합니다.


0

Chrome 개발자 도구를 사용하여 메모리 누수를 식별하는 것과 관련된 몇 가지 중요한 참고 사항 :

1) Chrome 자체에는 비밀번호 및 숫자 필드와 같은 특정 요소에 대한 메모리 누수가 있습니다. https://bugs.chromium.org/p/chromium/issues/detail?id=967438 . 분리 된 요소를 검색 할 때 힙 스냅 샷을 오염 시키므로 디버깅 중에는 사용하지 마십시오.

2) 브라우저 콘솔에 아무것도 기록하지 마십시오 . Chrome은 콘솔에 작성된 객체를 가비지 수집하지 않으므로 결과에 영향을줍니다. 스크립트 / 페이지의 시작 부분에 다음 코드를 배치하여 출력을 억제 할 수 있습니다.

console.log = function() {};
console.warn = console.log;
console.error = console.log;

3) 힙 스냅 샷을 사용하고 "분리"를 검색하여 분리 된 DOM 요소를 식별하십시오. 객체를 가리켜 서 각 요소를 식별하는 데 도움이되는 idouterHTML 을 포함한 모든 속성에 액세스 할 수 있습니다 . 분리 된 DOM 요소에 대한 세부 사항이있는 JS 힙 스냅 샷의 스크린 샷 분리 된 요소가 여전히 인식하기에 너무 일반적인 경우 테스트를 실행하기 전에 브라우저 콘솔을 사용하여 고유 한 ID를 지정하십시오. 예 :

var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
    divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory

이제 분리 된 요소를 식별하면 id = "AutoId_49"라고 말하고 페이지를 다시로드하고 위의 스 니펫을 다시 실행 한 다음 DOM 관리자 또는 document.querySelector (..)를 사용하여 id = "AutoId_49"로 요소를 찾으십시오. . 당연히 이것은 페이지 콘텐츠가 예측 가능한 경우에만 작동합니다.

테스트를 실행하여 메모리 누수를 식별하는 방법

1)로드 페이지 (콘솔 출력이 억제 된 상태)

2) 페이지에 메모리 누수를 일으킬 수있는 작업을 수행하십시오.

3) 개발자 도구를 사용하여 힙 스냅 샷을 작성하고 "분리"를 검색하십시오.

4) 요소를 가리켜 id 또는 outerHTML 속성 에서 식별


또한 브라우저에서 디버깅하기가 더 어려워 지므로 축소 / 축소를 비활성화하는 것이 좋습니다.
지미 톰슨
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.