티스토리 뷰

반응형

Memory leak check sample

 

이번에 내용은 메모리 누수 현상(Memory leak)을 눈으로 확인 할 수 있는 MemoryViewer의 소스 코드와 사용법, 사용 이미지를 포스트 한다.

MemoryViewer는 사용 메모리 양을 알 수 있는 것은 아니지만, 객체가 살아 있는지 죽었는지 눈으로 확인이 가능하여, 매우 유용하게 사용 할 수 있을 것이다.

 

 

1. 메모리 누수(Memory leak)

앱 개발시에 사용된 모든 내용(View, ViewModel, Model..등)은 메모리를 로드(Instance)가 된다. 그리고, 페이지를 이쪽 저쪽으로 이동하고, 여러가지 작업을 하다보면, 어느덧 메모리 사용량이 증가를 하게 되는데,..

 

 

기본적으로 메모리 관리는 GC(Garbage Collection)가 관리하지만, 이 녀석이 만능은 아니라서, 객체와 객체가 서로 연결되어 있는 상태라면, 연결이 끊어지지 않는한은 해제가 잘 되지 않는다. 자세한 사항은 자신의 프로젝트를 떠올려보면 알 수 있을 것이라고 생각한다.

 

 

그래서, 그런 메모리 누수가 지속적으로 발생하면, 아래 이미지와 비슷하게 사용되지 않는 공간의 메모리가 늘어나서 시스템에 영향을 미치게 되는 것이다.

 

 

 

2. MemoryItemM.cs

 

    /// <summary>
    /// MemoryItemModel, 메모리 아이템의 기본 모델
    /// </summary>
    public class MemoryItemM : BindableBase
    {
        private string _className;
        private IList<WeakReference> _weakReferences;

        /// <summary>
        ///     기본생성자
        /// </summary>
        public MemoryItemM()
        {
        }

        /// <summary>
        ///     생성자
        /// </summary>
        /// <param name="obj"></param>
        public MemoryItemM(object obj)
        {
            if (obj == null) return;
            ClassName = obj.GetType().FullName;
            AddObject(obj);
        }

        /// <summary>
        ///     클래스명
        /// </summary>
        public string ClassName
        {
            get { return _className; }
            private set { SetProperty(ref _className, value); }
        }

        /// <summary>
        ///     WeakReferences
        /// </summary>
        public IList<WeakReference> WeakReferences
        {
            get { return _weakReferences ?? (_weakReferences = new ObservableCollection<WeakReference>()); }
        }

        /// <summary>
        ///     AddObject
        /// </summary>
        /// <param name="obj"></param>
        public void AddObject(object obj)
        {
            if (string.IsNullOrEmpty(ClassName)
                || !ClassName.Equals(obj.GetType().FullName)) return;

            WeakReferences.Add(new WeakReference(obj));
        }

        /// <summary>
        ///     WeakReferences에 연결된 오브젝트 존재여부
        /// </summary>
        /// <returns></returns>
        public bool IsAliveWeakReferences()
        {
            List<WeakReference> deads = WeakReferences.Where(p => p.IsAlive.Equals(false)).ToList();
            if (deads.Any())
            {
                foreach (WeakReference dead in deads)
                {
                    WeakReferences.Remove(dead);
                }
            }
            return WeakReferences.Any();
        }

        /// <summary>
        ///     obj 존재여부 확인
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool IsExist(object obj)
        {
            return WeakReferences.FirstOrDefault(p => p.Target.Equals(obj)) != null;
        }
    }

 

 

3. MemoryViewerVM.cs

 

    /// <summary>
    /// MemoryViewerViewModel
    /// </summary>
    public class MemoryViewerVM : BindableBase
    {
        private ICollection<MemoryItemM> _memoryItems;

        /// <summary>
        ///     기본생성자
        /// </summary>
        public MemoryViewerVM()
        {
            MemoryItems = new ObservableCollection<MemoryItemM>();

            if (DesignMode.DesignModeEnabled)
            {
                MemoryItems.Add(new MemoryItemM("Test1"));
                MemoryItems.Add(new MemoryItemM("Test2"));
                MemoryItems.Add(new MemoryItemM("Test3"));
                MemoryItems.Add(new MemoryItemM("Test4"));
                MemoryItems.Add(new MemoryItemM("Test5"));
            }
            else
            {
#if DEBUG
                var dt = new DispatcherTimer {Interval = TimeSpan.FromSeconds(5)};
                dt.Tick += (s, e) => CheckMemoryItems();
                dt.Start();
#endif
            }
        }

        /// <summary>
        /// MemoryItems, 인스턴스된 객체 목록
        /// </summary>
        public ICollection<MemoryItemM> MemoryItems
        {
            get { return _memoryItems; }
            private set { SetProperty(ref _memoryItems, value); }
        }

        /// <summary>
        /// 메모리 아이템 체크, 5초에 한번씩 메모리를 확인해서 제거된 아이템을 삭제한다.
        /// </summary>
        private void CheckMemoryItems()
        {
            //MemoryItemM자체를 체크
            List<MemoryItemM> deads = MemoryItems.Where(p => p.IsAliveWeakReferences().Equals(false)).ToList();
            if (deads.Any())
            {
                foreach (MemoryItemM dead in deads)
                {
                    MemoryItems.Remove(dead);
                }
            }
        }

        /// <summary>
        ///     Object 추가
        /// </summary>
        /// <param name="obj"></param>
        public void AddObject(object obj)
        {
            CheckMemoryItems();

            //동일한 object가 이미 존재하는 경우
            MemoryItemM existItem = MemoryItems.FirstOrDefault(p => p.IsExist(obj));
            if (existItem != null) return;

            //동일한 이름의 MemoryItemM이 존재하는 경우
            MemoryItemM existItemName = MemoryItems.FirstOrDefault(p => p.ClassName.Equals(obj.GetType().FullName));
            if (existItemName != null)
            {
                existItemName.AddObject(obj);
            }
            else
            {
                MemoryItems.Add(new MemoryItemM(obj));
            }

            CheckMemoryItems();
        }
    }

 

 

4. MemoryViewer.xaml

 

<UserControl
    x:Class="UniversalSample.MemoryViewer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UniversalSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModels="using:UniversalSample.ViewModels"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <d:DesignInstance.DataContext>
        <viewModels:MemoryViewerVM />
    </d:DesignInstance.DataContext>

    <Grid>
        <Border IsHitTestVisible="False" Background="DarkSeaGreen">
            <ListBox Margin="5" Background="Transparent" HorizontalContentAlignment="Stretch"
                     VerticalContentAlignment="Stretch" ItemsSource="{Binding MemoryItems}" >
                <ListBox.ItemContainerStyle>
                    <Style TargetType="ContentControl">
                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                    </Style>
                </ListBox.ItemContainerStyle>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Text="{Binding ClassName, Mode=OneWay}"/>
                            <TextBlock Grid.Column="1" HorizontalAlignment="Right" Margin="10,0,0,0">
                                <Run Text="COUNT:"/>
                                <Run Text="{Binding WeakReferences.Count}"/>
                            </TextBlock>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

        </Border>
    </Grid>
</UserControl> 

 

 

5. MemoryView.xaml.cs

 

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
using Windows.ApplicationModel;
using Windows.UI.Xaml.Controls;
using UniversalSample.ViewModels;

namespace UniversalSample
{
    public sealed partial class MemoryViewer : UserControl
    {
        public MemoryViewer()
        {
            InitializeComponent();

            if (DesignMode.DesignModeEnabled == false)
            {
                DataContext = DataContext ?? ViewModelLocator.MemoryViewer;
            }
        }
    }
}

 

 

6. 사용법

 

MemoryViewerVM에 있는 AddObject 메소드를 이용해서 자신이 보고 싶은 객체를 추가해 주면 된다.

 

자기 자신 추가(View나 ViewModel이나..어느 것이라도..)

ViewModelLocator.MemoryViewer.AddObject(this);

 

객체를 생성 후 추가

var popup = new Popup();

ViewModelLocator.MemoryViewer.AddObject(popup);

 

 

7. 화면

 

첫화면에 있는 MemoryView 버튼을 클릭해서 팝업을 출력한다.

이미지를 하나 불러오니 리스트에 BitmapImage가 추가되었다.

 

Popup Test 버튼을 눌러서 팝업을 추가하니 TestPopup과 Popup 객체가 리스트에 추가된다.

팝업 출력을 5번 정도 반복하면 5개의 객체가 만들어 진것을 볼 수 있다.

더 이상 팝업을 출력하지 않고 시간을 끌고 있으면, 팝업이 메모리에서 해제 된 것을 볼 수 있다.

 

 

 

8. 소스

 

UniversalSample_MemoryViewer.zip

 

반응형
댓글