연습: 메모리 누수 찾기(JavaScript)Walkthrough: Find a memory leak (JavaScript)

Windows 및 Windows Phone에 적용됨Applies to Windows and Windows Phone

이 연습에서는 JavaScript 메모리 분석기를 사용하여 간단한 메모리 문제를 식별하고 수정하는 과정을 안내합니다.This walkthrough leads you through the process of identifying and fixing a simple memory issue by using the JavaScript memory analyzer. JavaScript를 사용하여 Windows용으로 작성된 UWP 앱에 대한 JavaScript 메모리 분석기는 Visual Studio에서 사용할 수 있습니다.The JavaScript memory analyzer is available in Visual Studio for UWP apps built for Windows using JavaScript. 이 시나리오에서는 DOM 요소를 만들어질 때와 같은 속도로 삭제하지 않고 메모리에 잘못 유지하는 앱을 만듭니다.In this scenario, you create an app that incorrectly retains DOM elements in memory instead of disposing of elements at the same rate in which they are created.

이 앱의 메모리 누수 원인은 매우 구체적이지만 여기에 나와 있는 단계에서는 메모리가 누수되는 개체를 격리하는 데 일반적으로 효과적인 워크플로를 보여줍니다.Although the cause of the memory leak in this app is very specific, the steps shown here demonstrate a workflow that is typically effective in isolating objects that are leaking memory.

JavaScript 메모리 분석기 테스트 앱 실행Running the JavaScript memory analyzer test app

  1. Visual Studio에서 파일, 새로 만들기, 프로젝트를 선택합니다.In Visual Studio, choose File, New, Project.

  2. 왼쪽 창에서 JavaScript 를 선택하고 Windows, Windows 8을 선택한 다음 유니버설 또는 Windows Phone 앱을 선택합니다.Choose JavaScript in the left pane, and then choose Windows, Windows 8, then either Universal or Windows Phone Apps.

    중요

    이 항목에 표시된 메모리 사용 결과는 Windows 8 앱에 대해 테스트됩니다.The memory usage results shown in this topic are tested against a Windows 8 app.

  3. 가운데 창에서 새 앱 프로젝트 템플릿을 선택합니다.Choose the Blank App project template in the middle pane.

  4. 이름 상자에 JS_Mem_Tester와 같은 이름을 지정한 다음 확인을 선택합니다.In the Name box, specify a name such as JS_Mem_Tester, and then choose OK.

  5. 솔루션 탐색기에서 default.html을 열고 <body> 태그 사이에 다음 코드를 붙여 넣습니다.In Solution Explorer, open default.html and paste the following code between the <body> tags:

    <div class="wrapper">  
        <div id="item"></div>  
        <button class="memleak" style="display: block" >Leak Memory</button>  
    </div>  
    

    중요

    Windows 8.1유니버설 앱 템플릿을 사용하는 경우 .Windows 프로젝트와 .WindowsPhone 프로젝트 모두에서 HTML 및 CSS 코드를 업데이트해야 합니다.If you are using a Windows 8.1 universal app template, you need to update HTML and CSS code in both the .Windows and the .WindowsPhone projects.

  6. default.css를 열고 다음 CSS 코드를 추가합니다.Open default.css and add the following CSS code:

    .memleak {  
        position: absolute; top: 100px; left: 100px;  
    }  
    
  7. default.js를 열고 모든 코드를 이 코드로 바꿉니다.Open default.js and replace all the code with this code:

    (function () {  
        "use strict";  
    
        var app = WinJS.Application;  
        var activation = Windows.ApplicationModel.Activation;  
    
        var wrapper;  
        var elem;  
    
        app.onactivated = function (args) {  
            if (args.detail.kind === activation.ActivationKind.launch) {  
                if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {  
                } else {  
                }  
                args.setPromise(WinJS.UI.processAll());  
    
                elem = document.getElementById("item");  
                wrapper = document.querySelector(".wrapper");  
                var btn = document.querySelector(".memleak");  
                btn.addEventListener("click", btnHandler);  
                run();  
            }  
        };  
    
        app.oncheckpoint = function (args) {  
        };  
    
        app.start();  
    
        function run() {  
            initialize();  
            load();  
        }  
    
        function initialize() {  
    
            if (wrapper != null) {  
                elem.removeNode(true);  
            }  
        }  
    
        function load() {  
    
            var newDiv = document.createElement("div");  
    
            newDiv.style.zIndex = "-1";  
            newDiv.id = "item";  
    
            wrapper.appendChild(newDiv);  
        }  
    
        function btnHandler(args) {  
            run();  
        }  
    
    })();  
    
  8. F5 키를 선택하여 디버깅을 시작합니다.Choose the F5 key to start debugging. 페이지에 메모리 누수 단추가 나타나는지 확인합니다.Verify that the Leak Memory button appears on the page.

  9. Alt+Tab을 눌러 Visual Studio로 다시 전환하고 Shift+F5를 눌러 디버깅을 중지합니다.Switch back to Visual Studio (Alt+Tab), and then choose Shift+F5 to stop debugging.

    앱 작동을 확인했으므로 메모리 사용을 검사할 수 있습니다.Now that you've verified that the app works, you can examine the memory usage.

메모리 사용 분석Analyzing the memory usage

  1. 디버그 도구 모음의 디버깅 시작 목록에서 업데이트된 프로젝트의 디버그 대상을 Windows Phone 에뮬레이터 또는 시뮬레이터중에 선택합니다.On the Debug toolbar, in the Start Debugging list, choose the debug target for the updated project: either one of the Windows Phone Emulators or Simulator.

    UWP 앱의 경우 이 목록에서 로컬 컴퓨터 또는 원격 컴퓨터를 선택할 수도 있습니다.For a UWP app, you can also choose Local Machine or Remote Machine in this list.

  2. 디버그 메뉴에서 성능 프로파일러...를 선택합니다.On the Debug menu, choose Performance Profiler....

  3. 사용 가능한 도구에서 JavaScript 메모리를 선택한 다음 시작을 선택합니다.In Available Tools, choose JavaScript Memory, and then choose Start.

    이 자습서에서는 시작 프로젝트에 메모리 분석기를 연결합니다.In this tutorial, you'll be attaching the memory analyzer to the startup project. 설치된 앱에 메모리 분석기 연결 등의 기타 옵션에 대한 자세한 내용은 JavaScript 메모리를 선택합니다.For info about other options, like attaching the memory analyzer to an installed app, see JavaScript Memory.

    메모리 분석기를 시작하면 VsEtwCollector.exe 실행 권한을 요청하는 사용자 계정 컨트롤이 표시될 수 있습니다.When you start the memory analyzer, you might see a User Account Control requesting your permission to run VsEtwCollector.exe. 를 선택합니다.Choose Yes.

  4. 메모리 누수 단추를 연속해서 네 번 선택합니다.Choose the Leak Memory button four times in succession.

    단추를 선택하면 default.js의 이벤트 처리 코드가 메모리 누수를 발생시킵니다.When you choose the button, the event handling code in default.js does work that will result in a memory leak. 이것을 진단 용도로 사용합니다.You'll use this for diagnostic purposes.

    메모리 누수가 있는지 테스트할 시나리오를 반복하면 앱 초기화 중 또는 페이지를 로드할 때 힙에 추가되는 개체 등 필요하지 않은 정보를 더 쉽게 필터링할 수 있습니다.Repeating the scenario that you want to test for a memory leak makes it easier to filter out uninteresting info, such as objects that are added to the heap during app initialization or when loading a page.

  5. Alt+Tab을 눌러 실행 중인 응용 프로그램에서 Visual Studio로 전환합니다.From the running app, switch to Visual Studio (Alt+Tab).

    Visual Studio 새 탭에 JavaScript 메모리 분석기가 표시됩니다.The JavaScript memory analyzer displays information in a new tab in Visual Studio.

    이 요약 뷰의 메모리 그래프는 시간별 프로세스 메모리 사용을 보여 줍니다.The memory graph in this summary view shows process memory usage over time. 뷰에서는 힙 스냅숏 만들기와 같은 명령도 제공합니다.The view also provides commands like Take heap snapshot. 스냅숏은 특정 시간의 메모리 사용에 대한 자세한 정보를 제공합니다.A snapshot provides detailed information about memory usage at a particular time. 자세한 내용은 JavaScript 메모리를 선택합니다.For more info, see JavaScript Memory.

  6. 힙 스냅숏 만들기를 클릭합니다.Choose Take heap snapshot.

  7. 앱으로 전환하고 메모리 누수를 선택합니다.Switch to the app and choose Leak Memory.

  8. Visual Studio로 전환하고 힙 스냅숏 만들기 를 다시 선택합니다.Switch to Visual Studio and choose Take heap snapshot again.

    이 그림에서는 기본 스냅숏(#1)과 스냅숏 #2를 보여 줍니다.This illustration shows the baseline snapshot (#1) and Snapshot #2.

    기준 스냅숏 및 스냅숏 2The baseline snapshot and snapshot 2

    참고

    Windows Phone 에뮬레이터는 스냅숏이 만들어진 때의 앱 스냅숏을 보여 주지 않습니다.The Windows Phone Emulator does not show a screenshot of the app at the time the snapshot was taken.

  9. 앱으로 전환하고 메모리 누수 단추를 다시 선택합니다.Switch to the app and choose the Leak Memory button again.

  10. Visual Studio로 전환하고 세 번째 힙 스냅숏 만들기 를 선택합니다.Switch to Visual Studio and choose Take heap snapshot for the third time.

    이 워크플로의 세 번째 스냅숏을 작성하면 기본 스냅숏을 기준으로 메모리 누수와 연결되지 않은 두 번째 스냅숏에서 변경된 사항을 필터링할 수 있습니다.By taking a third snapshot in this workflow, you can filter out changes from the baseline snapshot to the second snapshot that aren't associated with memory leaks. 예를 들어 페이지에서 머리글 및 바닥글 업데이트와 같이 메모리 사용량을 변경하지만 메모리 누수와는 관련이 없을 수 있는 변경 사항이 예상될 수 있습니다.For example, there may be expected changes such as updating headers and footers on a page, which will generate some changes in memory usage but may be unrelated to memory leaks.

    이 그림에서는 스냅숏 #2와 스냅숏 #3을 보여 줍니다.This illustration shows Snapshot #2 and Snapshot #3.

    스냅숏 2 및 스냅숏 3Snapshot 2 and snapshot 3

  11. Visual Studio에서 중지 를 선택하여 프로파일링을 중지합니다.In Visual Studio, choose Stop to stop profiling.

  12. Visual Studio에서 스냅숏을 비교합니다.In Visual Studio, compare the snapshots. 스냅숏 #2는 다음을 보여 줍니다.Snapshot #2 shows the following:

    • 스냅숏 #1에 비해 힙 크기(왼쪽에 표시된 빨간색 위쪽 화살표)가 몇 KB 증가했습니다.The heap size (shown by the red up arrow on the left) has increased by several KB compared to Snapshot #1.

      중요

      힙 크기에 대한 정확한 메모리 사용량 값은 디버그 대상에 따라 달라집니다.Exact memory usage values for the heap size depend on the debug target.

    • 스냅숏 #1에 비해 힙의 개체 수(오른쪽에 표시된 빨간색 위쪽 화살표)가 증가했습니다.The number of objects on the heap (shown by the red up arrow on the right) has increased compared to Snapshot #1. 개체 1개가 추가되고(+1) 제거된 개체는 없습니다(-0).One object has been added (+1) and no objects have been removed (-0).

      스냅숏 #3는 다음을 보여 줍니다.Snapshot #3 shows the following:

    • 힙 크기가 스냅샷 #2에 비해 다시 몇 백 바이트 이상 증가했습니다.The heap size has increased again by several hundred bytes compared to Snapshot #2.

    • 힙의 개체 수가 스냅샷 #2에 비해 다시 증가했습니다.The number of objects on the heap has increased again compared to Snapshot #2. 개체 1개가 추가되고(+1) 제거된 개체는 없습니다(-0).One object has been added (+1) and no objects have been removed (-0).

  13. 스냅샷 #3에서 빨간색 위쪽 화살표 옆에 +1/-0 값이 있는 오른쪽 링크 텍스트를 선택합니다.In Snapshot #3, choose the link text on the right, which shows a value of +1 / -0 next to the red up arrow.

    힙 개체의 다른 뷰에 연결Link to different view of heap objects

    기본적으로 표시되는 형식 뷰와 함께 힙에 있는 개체에 대한 차이 뷰( 스냅숏 #3 - 스냅숏 #2)가 열립니다.This opens a differential view of the objects on the heap, called Snapshot #3 - Snapshot #2, with the Types view showing by default. 기본적으로 스냅숏 #2와 스냅숏 #3 사이의 힙에 추가된 개체의 목록이 표시됩니다.By default, you see a list of objects added to the heap between Snapshot #2 and Snapshot #3.

  14. 범위 필터에서 스냅숏 #2에서 남은 개체를 선택합니다.In the Scope filter, choose Objects left over from Snapshot #2.

  15. 다음과 같이 개체 트리의 맨위에 있는 HTMLDivElement 개체를 엽니다.Open the HTMLDivElement object at the top of the object tree as shown here.

    힙의 개체 개수 차이 뷰Diff view of the object count on the heap

    이 뷰에서는 다음과 같이 메모리 누수에 대한 유용한 정보를 보여 줍니다.This view shows helpful information about the memory leak, such as the following:

    • 이 뷰에서는 ID가 item인 DIV 요소를 보여주고 개체의 보존 크기는 몇 백 바이트입니다(정확한 값은 다를 수 있음).This view shows a DIV element with an ID of item, and the retained size for the object is several hundred bytes (exact value will vary).

    • 이 개체는 스냅숏 #2에서 남겨진 개체이고 잠재적인 메모리 누수를 나타냅니다.This object is a leftover object from Snapshot #2 and represents a potential memory leak.

      이 시점에서 앱에 대한 약간의 지식이 도움이 됩니다. 메모리 누수 단추를 선택하면 DIV 요소가 제거되고 요소가 추가되므로 코드가 제대로 작동하지 않습니다(즉, 메모리가 누수됨).Some knowledge of the app helps at this point: Choosing the Leak Memory button should remove a DIV element and add an element, so the code doesn't seem to be working right (that is, it leaks memory). 다음 섹션에서는 이 문제를 해결하는 방법을 설명합니다.The next section explains how to fix that.

    경우에 따라 Global 개체와 관련하여 개체를 찾으면 해당 개체를 쉽게 확인할 수 있습니다.Sometimes, locating an object in relation to the Global object may help identify that object. 이렇게 하려면 식별자에 대한 바로 가기 메뉴를 열고 루트 뷰에서 보기를 선택합니다.To do this, open the shortcut menu for the identifier, and then choose Show in roots view.

메모리 문제 수정Fixing the memory issue

  1. 프로파일러에 의해 표시된 데이터를 사용하여 ID가 "item"인 DOM 요소 제거를 담당하는 코드를 검사합니다.Using data revealed by the profiler, you examine code that is responsible for removing DOM elements with an ID of "item". 이 작업은 initialize() 함수에서 수행됩니다.That occurs in the initialize() function.

    function initialize() {  
    
        if (wrapper != null) {  
            elem.removeNode(true);  
        }  
    }  
    

    'elem.removeNode(true) 는 올바르게 작동하지 않을 것입니다.elem.removeNode(true) is, perhaps, not working correctly. 코드가 DOM 요소를 어떻게 캐시하는지 검사하고 문제를 찾습니다. 캐시된 요소에 대한 참조는 업데이트되지 않습니다.You examine how the code is caching the DOM element and find an issue; the reference to the cached element is not getting updated.

  2. default.js에서 appendChild호출 직전에 다음 코드 줄을 load 함수에 추가합니다.In default.js, add the following line of code to the load function, just before calling appendChild:

    elem = newDiv;  
    

    이 코드는 단추를 선택할 때 메모리 누수 요소가 올바르게 제거되도록 캐시된 요소에 대한 참조를 업데이트합니다.This code updates the reference to the cached element so that the element is correctly removed when you choose the Leak Memory button. load 함수에 대한 전체 코드는 다음과 같이 나타납니다.The complete code for the load function now looks like this:

    function load() {  
    
        wrapper = document.querySelector(".wrapper");  
    
        var newDiv = document.createElement("div");  
    
        newDiv.style.zIndex = "-1";  
        newDiv.id = "item";  
        elem = newDiv;  
    
        wrapper.appendChild(newDiv);  
    }  
    
  3. 디버그 메뉴에서 성능 및 진단을 선택합니다.On the Debug menu, choose Performance and Diagnostics.

  4. 사용 가능한 도구에서 JavaScript 메모리를 선택한 다음 시작을 선택합니다.In Available Tools, choose JavaScript Memory, and then choose Start.

  5. 앞에서와 동일한 절차에 따라 세 개의 스냅샷을 만듭니다.Follow the same procedure as before to take three snapshots. 단계는 여기에 요약되어 있습니다.The steps are summarized here:

    1. 앱에서 메모리 누수 단추를 연속해서 네 번 선택합니다.In the app, choose the Leak Memory button four times in succession.

    2. Visual Studio로 전환하고 기본 스냅숏에 대한 힙 스냅숏 만들기 를 선택합니다.Switch to Visual Studio and choose Take heap snapshot for the baseline snapshot.

    3. 앱에서 메모리 누수 단추를 선택합니다.In the app, choose the Leak Memory button.

    4. Visual Studio로 전환하고 두 번째 스냅숏에 대한 힙 스냅숏 만들기 를 선택합니다.Switch to Visual Studio and choose Take heap snapshot for the second snapshot.

    5. 앱에서 메모리 누수 단추를 선택합니다.In the app, choose the Leak Memory button.

    6. Visual Studio로 전환하고 세 번째 스냅숏에 대한 힙 스냅숏 만들기 를 선택합니다.Switch to Visual Studio and choose Take heap snapshot for the third snapshot.

      스냅숏 #3의 힙 크기는 이제 스냅숏 #2에 비해 증가 안 함 으로 나타나고 개체 수는 +2/-2로 표시됩니다. 이는 한 개의 개체가 추가되고 한 개의 개체가 제거되었다는 의미입니다.Snapshot #3 now shows the heap size as No increase from Snapshot #2, and the object count as +1 / -1, which indicates that one objects has been added and one object has been removed. 이러한 동작이 발생해야 합니다.This is the desired behavior.

      다음 그림에서는 스냅숏 #2와 스냅숏 #3을 보여 줍니다.The following illustration shows Snapshot #2 and Snapshot #3.

      수정된 메모리 누수를 보여주는 스냅숏Snapshots showing the fixed memory leak

참고 항목See Also

JavaScript 메모리JavaScript Memory