Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 5개 있습니다.)

WPF의 Window 객체를 생성했는데 GC 수집 대상이 안 되는 이유

질문이 하나 있군요.

안녕하세요 WPF 에서 Window객체가 가비지 콜렉션에 의해 수집되지 않는거 같아서 문의드립니다.
; https://www.sysnet.pe.kr/3/0/4896

실제로 WPF에서 다음과 같은 간단한 소스 코드로,

using System.Linq;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            foreach (int n in Enumerable.Range(1, 100000))
            {
                Window win = new Window();
            }
        }
    }
}

버튼 클릭 몇 번을 하면 OOM 예외가 발생하도록 만들 수 있습니다. 얼핏 보면 new Window만 했으니 GC 대상이 되어야 하지만 그렇지 않은 현상을 보이는 것입니다.

이런 경우 생각할 여지가 별로 없으니 다행입니다. 수행된 코드는 어차피 생성자 뿐이 없으니 그 안에서 아마도 다른 루트 객체에 자신의 참조를 추가했을 것입니다. 확인을 위해 .NET Reflector 등으로 소스 코드를 확인하면 답이 나오겠지요.

실제로 PresentationFramework 어셈블리의 System.Windows.Window 타입의 생성자를 찾아보면,

[SecurityCritical]
public Window()
{
    this._ownerHandle = IntPtr.Zero;
    this._updateHwndSize = true;
    this._updateHwndLocation = true;
    this._trackMaxWidthDeviceUnits = double.PositiveInfinity;
    this._trackMaxHeightDeviceUnits = double.PositiveInfinity;
    this._windowMaxWidthDeviceUnits = double.PositiveInfinity;
    this._windowMaxHeightDeviceUnits = double.PositiveInfinity;
    this._actualTop = double.NaN;
    this._actualLeft = double.NaN;
    this._dialogOwnerHandle = IntPtr.Zero;
    this._prePanningLocation = new Point(double.NaN, double.NaN);
    SecurityHelper.DemandUnmanagedCode();
    this._inTrustedSubWindow = false;
    this.Initialize();
}

다른 코드는 상관이 없으니 이젠 Initialize를 의심해 볼 수 있습니다.

private void Initialize()
{
    base.BypassLayoutPolicies = true;
    if (this.IsInsideApp)
    {
        if (Application.Current.Dispatcher.Thread == Dispatcher.CurrentDispatcher.Thread)
        {
            this.App.WindowsInternal.Add(this);
            if (this.App.MainWindow == null)
            {
                this.App.MainWindow = this;
            }
        }
        else
        {
            this.App.NonAppWindowsInternal.Add(this);
        }
    }
}

아하... 여기 있군요. WindowsInternal이든 NonAppWindowsInternal이든 자신을 추가하고 있습니다. 이렇게 this.App 객체가 참조를 잡아 두고 있으니 GC 해제가 안된 것입니다.

만약 참조 해제를 하고 싶다면 명시적으로 Close 메서드를 호출해 주면 됩니다.

foreach (int n in Enumerable.Range(1, 100000))
{
    Window win = new Window();
    win.Close();   
}

그런데, 조금 궁금한 부분이 있군요. 왜 WPF 개발팀은 Window 객체에 명시적으로 자원 해제를 위한 IDisposable 인터페이스를 구현하지 않았을까요?




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/19/2017]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2017-09-20 12시25분
[장진국] 친절한 답변 감사합니다^^ 덕분에 궁금중이 해결되었습니다.


[guest]
2017-09-20 02시12분
[이성환] 안녕하세요.

이미 본문에서 잘 설명이 되어 있지만 약간의 사족을 달아봅니다.

WPF 에서는 WindowBase 를 이용한 Window 생성 시 생성되는 모든 Window를
Application 클래스의 WindowsInternal 이라는 WindowsCollection 타입의 컬렉션 프로퍼티에 보관하도록 되어 있습니다.
(본문에 나오는 App 프로퍼티 역시 Window 클래스에서 Application.Current 형태로 보관하는 Application 객체입니다.)

따라서 Window 를 생성하면 Application의 WindowsInternal 에 추가되었다가 Close 되면 제거됩니다.
(본문에서 설명한 것과 같이 Show() 호출이 아니라 new 로 생성하여 Initialize() 가 호출되면 바로 등록됩니다. 그렇기 때문에 생성된 Window는 Close() 호출 전까지 Application에서 참조됩니다.)

Internal 이라는 접미로 알 수 있듯이 이 프로퍼티의 접근제한자는 internal 이며 이 프로퍼티를 public으로 노출하는 것이
Application 클래스의 Windows 라는 프로퍼티 입니다.
이 Windows 프로퍼티는 WindowsInternal을 clone 하여 반환하긴 하지만 Deep copy 가 아니라 컬렉션 객체를 옮겨 담는 형태로 copy됩니다.

사용자는 public으로 노출된 Windows 프로퍼티에 접근해 현재 생성된 Window 들의 정보를 사용할 수 있습니다. (WindowsManager 같은 것을 만들 때 유용합니다.)
이 때 가장 먼저 만들어지는 Window 객체를 Application 클래스의 MainWindow라는 별도의 프로퍼티에 따로 할당하여 사용하는데
이 MainWindow 는 특별한 타입이 아니라 그냥 Window 타입이며 ShutdownMode 같은 영역에 기준으로 사용되기도 합니다.
(MainWindow는 수동으로 재할당이 가능합니다.)

WPF의 Window 관리를 인지하지 못하고 Window 를 생성하여 처리하면 메모리 누수로 이어질 가능성이 높기 때문에 주의가 필요합니다.

여기까지 알아두면 그럭저럭 쓸모있는 사족이었습니다.
[guest]
2017-09-20 09시12분
좋은 내용의 덧글 감사드립니다. ^^
정성태

... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12421정성태11/19/20208783.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법파일 다운로드1
12420정성태11/19/20207965오류 유형: 684. Visual C++ - MSIL .netmodule or module compiled with /GL found; restarting link with /LTCG; add /LTCG to the link command line to improve linker performance
12419정성태11/19/20209243VC++: 139. Visual C++ - .NET Core의 nethost.lib와 정적 링크파일 다운로드1
12418정성태11/19/202011265오류 유형: 683. Visual C++ - error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MDd_DynamicDebug'파일 다운로드1
12417정성태11/19/20208685오류 유형: 682. Visual C++ - warning LNK4099: PDB '...pdb' was not found with '...lib(pch.obj)' or at '...pdb'; linking object as if no debug info
12416정성태11/19/20209967오류 유형: 681. Visual C++ - error LNK2001: unresolved external symbol _CrtDbgReport
12415정성태11/18/202010083.NET Framework: 971. UnmanagedCallersOnly 특성과 DNNE 사용파일 다운로드1
12414정성태11/18/202011105VC++: 138. x64 빌드에서 extern "C"가 아닌 경우 ___cdecl name mangling 적용 [4]파일 다운로드1
12413정성태11/17/202010797.NET Framework: 970. .NET 5 / .NET Core - UnmanagedCallersOnly 특성을 사용한 함수 내보내기파일 다운로드1
12412정성태11/16/202013033.NET Framework: 969. .NET Framework 및 .NET 5 - UnmanagedCallersOnly 특성 사용파일 다운로드1
12411정성태11/12/202010024오류 유형: 680. C# 9.0 - Error CS8889 The target runtime doesn't support extensible or runtime-environment default calling conventions.
12410정성태11/12/20209928디버깅 기술: 174. windbg - System.TypeLoadException 예외 분석 사례
12409정성태11/12/202011305.NET Framework: 968. C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법파일 다운로드1
12408정성태11/9/202022859도서: 시작하세요! C# 9.0 프로그래밍 [8]
12407정성태11/9/202011607.NET Framework: 967. "clr!JIT_DbgIsJustMyCode" 호출이 뭘까요?
12406정성태11/8/202013184.NET Framework: 966. C# 9.0 - (15) 최상위 문(Top-level statements) [5]파일 다운로드1
12405정성태11/8/202010601.NET Framework: 965. C# 9.0 - (14) 부분 메서드에 대한 새로운 기능(New features for partial methods)파일 다운로드1
12404정성태11/7/202011141.NET Framework: 964. C# 9.0 - (13) 모듈 이니셜라이저(Module initializers)파일 다운로드1
12403정성태11/7/202011179.NET Framework: 963. C# 9.0 - (12) foreach 루프에 대한 GetEnumerator 확장 메서드 지원(Extension GetEnumerator)파일 다운로드1
12402정성태11/7/202011789.NET Framework: 962. C# 9.0 - (11) 공변 반환 형식(Covariant return types) [1]파일 다운로드1
12401정성태11/5/202010698VS.NET IDE: 153. 닷넷 응용 프로그램에서의 "My Code" 범위와 "Enable Just My Code"의 역할 [1]
12400정성태11/5/20207664오류 유형: 679. Visual Studio - "Source Not Found" 창에 "Decompile source code" 링크가 없는 경우
12399정성태11/5/202011409.NET Framework: 961. C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)파일 다운로드1
12398정성태11/4/20209911오류 유형: 678. Windows Server 2008 R2 환경에서 Powershell을 psexec로 원격 실행할 때 hang이 발생하는 문제
12397정성태11/4/202010076.NET Framework: 960. C# - 조건 연산자(?:)를 사용하는 경우 달라지는 메서드 선택 사례파일 다운로드1
12396정성태11/3/20208383VS.NET IDE: 152. Visual Studio - "Tools" / "External Tools..."에 등록된 외부 명령어에 대한 단축키 설정 방법
... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...