티스토리 뷰

Memory Leak?

컴퓨터 과학에서 메모리 누수(memory leak)는 더 이상 필요하지 않은 메모리가 해제되지 않는 방식으로 컴퓨터 프로그램이 메모리 할당을 잘못 관리 할 때 발생하는 일종의 리소스 누수입니다. 객체가 메모리에 저장되어 있지만 실행중인 코드에서 액세스 할 수없는 경우에도 메모리 누수가 발생할 수 있습니다. 메모리 누수는 다른 여러 문제와 유사한 증상을 가지며 일반적으로 프로그램의 소스 코드에 액세스 할 수있는 프로그래머에 의해서만 진단 될 수 있습니다.

메모리 누수 셈플은 여기를 참고했습니다. 이 포스트에는 DotMemory, PerfView, OzCode를 이용하는 방법에 대해서도 설명이 있으니 참고하시면 좋을 것 같습니다.

메모리 누수 발생 샘플

MainWindow.xaml.cs

public partial class MainWindow : Window
    {
        List<ClockWindow> clocks = new();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Start_Click(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 50; i++)
            {
                var clock = new ClockWindow();
                clock.Show();
                clocks.Add(clock);
            }
        }

        private void Stop_Click(object sender, RoutedEventArgs e)
        {
            foreach (var clock in clocks)
            {
                clock.Close();
            }
            clocks.Clear();
        }
    }

ClickWindow.xaml.cs

public partial class ClockWindow : Window
    {
        DispatcherTimer timer;

        public ClockWindow()
        {
            InitializeComponent();

            timer = new DispatcherTimer
            {
                Interval = new TimeSpan(0, 0, 1)
            };

            timer.Start();
            timer.Tick += UpdateTime;
        }

        private void UpdateTime(object sender, EventArgs e)
        {
            timerText.Content = DateTime.Now.ToLongTimeString();
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
        }
    }

Memory Usage를 이용해서 메모리 누수 확인

실행합니다.

오른쪽 하단에 Memory Usage 탭에서 사진기 아이콘 버튼을 눌러서 첫번째 메모리 스냅샷을 생성합니다.

Start 버튼을 눌러서 50개의 ClockWindow를 생성합니다.

Stop 버튼을 눌러서 모든 ClockWindow를 닫습니다. 그리고 사진기 버튼을 눌러서 두번째 스냅샷을 생성합니다.

Objects의 화살표 부분을 클릭합니다.

2번째 스냅샷의 정보를 조회할 수 있습니다. 

상단에 Compare to : 옆의 콤보박스를 눌러서 Snapshot #1을 선택하면 첫번째 스냅샷과 두번째 스냅샷을 비교해서 차이가 나는 부분만 표시합니다.

차이나는 부분만 표시

오른쪽 필터 부분에 clockwindow를 입력합니다.

이름에서 clockwindow가 포함된 object 목록을 볼 수 있습니다.

모든 ClockWindow를 닫았는데도, 아래와 같이 50개의 ClockWindow가 존재하는 것을 확인할 수 있습니다.

이렇게 접근할 수 없는 영역에 ClockWindow가 남아있는 현상을 메모리 누수라고 이야기를 하며, 대부분의 경우 이벤트 핸들러와 관련된 문제 입니다.

문제 해결

ClockWindow를 선택하면 하단에 상세 정보가 표시되는데, 그 중 EventHandler 부분을 확장하면 DispatcherTimer가 보이고, DispatcherOperationCallback이라는 것이 표시되는 것을 알 수 있습니다. 

만약, 여러개의 이벤트 핸들러가 있다면 각 각의 내용들을 모두 점검을 하면서 찾는 과정이 필요합니다.

이벤트 핸들러가 표시되지 않는다면, 화면 종료시에 사용했던 모든 객체를 null로 만들어준 후 확인을 하시면 됩니다.

ClockWindow.xaml.cs

OnClosed 메소드에 timer.Tick -= UpdateTime;을 추가합니다.

using System;
using System.Windows;
using System.Windows.Threading;

namespace CoreWpfMemory
{
    /// <summary>
    /// Interaction logic for ClockWindow.xaml
    /// </summary>
    public partial class ClockWindow : Window
    {
        private readonly DispatcherTimer timer;

        public ClockWindow()
        {
            InitializeComponent();

            timer = new DispatcherTimer
            {
                Interval = new TimeSpan(0, 0, 1)
            };

            timer.Start();
            timer.Tick += UpdateTime;
        }

        private void UpdateTime(object sender, EventArgs e)
        {
            timerText.Content = DateTime.Now.ToLongTimeString();
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            //Uncommnet below lines to stop memory leak
            timer.Tick -= UpdateTime;
            timer.Stop();
        }
    }
}

실행 후 위와 같은 방법을 이용해서 확인합니다.

이전과는 다르게 ClockWindow 50개가 표시되지 않는 것을 확인 할 수 있습니다.

간단한 셈플 프로그램과 Visual Studio의 Memory Usage 기능을 이용해서 메모리 누수 확인 하는 방법을 알아 보았습니다. Memory Usage에 대한 더 자세한 사항은 여기를 참고합니다.

메모리 누수가 심한 경우에는 스냅샷을 촬영하지 못하는 경우도 많이 발생하기 때문에 전문 툴인 dotMemory를 이용하는 것도 좋습니다.

셈플 코드

kaki104/WpfTest (github.com)

댓글
  • 프로필사진 봉이봉봉 질문 하나 드려도 될지 모르겠지만 질문 하나만 하고 싶습니다!

    Thread와 BackgroundWorker와 Task 사용함에 있어
    구분이 모호 하지만 사용법에 따라 적절히 사용하면 되고 Task와 BackgroundWorker는 ThreadPool에서 Thread를 할당받아
    작업 진행하기 때문에 언제 시작되고 언제 종료 될지 애매하다 정도 정리가 되었는데요.

    현재 테스트 하고 있는게 Task와 Thread가 어떤 상황일 때 성능차이가 발생하느냐 하는 겁니다.
    Windows에서는 CPU 코어에 Process를 할당하고 Process 내부에서 생성한 사용자Thread와 Main UI Thread (비주얼 프로그래밍을 할때)가
    시 분할 방식으로 진행된다고 알고 있었는데 별로 그런것 같지 않은 것 같더라고요.

    제가 확인하고 싶었던 가설이 1개의 프로세스에서 Thread로 처리할 때는 프로세스 내부에서 시분할로 처리되기 때문에
    Task로 처리 할 경우 ThreadPool로 Task를 보내 처리하는 것임으로 CPU 사용량이 많을 때는
    프로세스 내부에서 자신의 시간을 기다리는 것 보다 Task가 처리가 더 빠를 것이라고 생각 했는데
    테스트 결과가 차이점이 없다고 생각 되는 결과가 나왔습니다.

    혹시 관련해서 확인 할 수 있는 자료나 검색 키워드에 관해 조언 받을 수 있을까요?
    이런질문 해도 되는지 모르고 질문 남겨 봅니다 혹여나 실례가 되었다면, 알려주시면 댓글 삭제하도록 하겠습니다.
    2021.07.20 15:23 신고
  • 프로필사진 Connor Park Task와 Thread의 차이점에 대한 문의인것 같아서 한번 찾아보았습니다.
    https://www.c-sharpcorner.com/article/task-and-thread-in-c-sharp/
    아래는 Thread와 BackgroundWorker에 대한 차이점에 대한 내용입니다.
    https://www.codeproject.com/Articles/628237/Thread-vs-BackgroundWorker
    도움이 되면 좋겠습니다.
    2021.08.09 19:57 신고
  • 프로필사진 봉이봉봉 감사합니다.

    그림설명이랑 치이점 구분이 되어있어서 어떻게 사용할지 이전 보다 명확해 진 것 같습니다!
    2021.09.02 10:30 신고
댓글쓰기 폼
Total
641,202
Today
116
Yesterday
311
«   2021/10   »
          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
31            
10-22 10:47
글 보관함