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분
좋은 내용의 덧글 감사드립니다. ^^
정성태

... 136  137  138  139  140  141  142  143  144  [145]  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1429정성태3/29/201323386개발 환경 구성: 188. SCOM 2012 - ASP.NET 모니터링 방법
1428정성태3/29/201324256개발 환경 구성: 187. SCOM 2012 환경 구성 - Management Packs
1427정성태3/29/201321314오류 유형: 171. SCOM 2012 - 원격 에이전트 설치 오류
1426정성태3/29/201324177개발 환경 구성: 186. SCOM 2012 환경 구성 - 관리 대상 추가
1424정성태3/21/201326043개발 환경 구성: 185. System Center 2012 Operations Manager 설치
1423정성태3/18/201320979오류 유형: 170. The specified domain either does not exist or could not be contacted.
1422정성태3/14/201323122오류 유형: 169. Windows 8/2012에 .NET 3.5가 설치되지 않는 경우
1421정성태3/13/201340455.NET Framework: 364. WCF RIA 서비스 + Silverlight 사용 예제
1420정성태3/12/201325145오류 유형: 168. ORA-12514: TNS:listener does not currently know of service requested in connect descriptor
1419정성태3/12/201321592Windows: 70. 관리 도구에서 "Windows Server Backup" 항목이 없는 경우
1418정성태2/28/201331548오류 유형: 167. Internet Explorer 10 설치 후 Flash 객체의 메서드/속성 접근 오류가 발생한다면?
1417정성태2/25/201327627.NET Framework: 363. ASP.NET AJAX PageMethods - ASPX.cs의 static 메서드를 AJAX로 호출파일 다운로드1
1416정성태2/22/201330234개발 환경 구성: 184. Xamarin 2.0 - Visual Studio에서 Android 앱을 폰으로 직접 배포하는 방법
1415정성태2/21/201337660개발 환경 구성: 183. Xamarin 2.0 - 윈도우 환경의 Visual Studio에서 C#으로 iOS/Android 응용 프로그램 개발 [4]파일 다운로드1
1414정성태2/21/201332805개발 환경 구성: 182. JMeter로 XML 웹 서비스 호출에 대한 부하 테스트 방법파일 다운로드2
1413정성태2/19/201332114VC++: 66. Chromium 컴파일하는 방법 - 두 번째 이야기 [3]
1412정성태2/6/201333998VC++: 65. Python 소스코드를 Visual C++로 빌드하는 방법 [3]
1411정성태1/31/201349306개발 환경 구성: 181. 무료 데이터베이스 서버 성능 비교(SQL Server Express, IBM DB2 Express, MySQL, Sybase, PostgreSQL, Oracle XE) [9]
1410정성태1/31/201331113.NET Framework: 362. C# - 닷넷 응용 프로그램에서 Sybase DB 사용 [1]파일 다운로드1
1409정성태1/30/201335083.NET Framework: 361. C# - 공유기 관리 웹 페이지 인증 [4]파일 다운로드1
1408정성태1/29/201329868.NET Framework: 360. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (2) [1]파일 다운로드1
1407정성태1/29/201328290.NET Framework: 359. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (1)
1406정성태1/22/201322741개발 환경 구성: 180. Windows Server 2012 RC 버전을 RTM으로 업그레이드하는 방법
1405정성태1/16/201344304.NET Framework: 358. C# - 닷넷 응용 프로그램에서 MySQL(MySqlConnector) 사용 [7]파일 다운로드1
1404정성태1/15/201329149개발 환경 구성: 179. Hyper-V VM 복사는 robocopy로. [2]
1403정성태1/14/201331977.NET Framework: 357. .NET 4.5의 2GB 힙 한계 극복
... 136  137  138  139  140  141  142  143  144  [145]  146  147  148  149  150  ...