성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>닷넷 GC가 순환 참조를 해제할 수 있을까요?</h1> <p> 아래와 같은 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c# 상호참조 질문 ; <a target='tab' href='http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_QnA_etc&page=1&page_num=35&select_arrange=last_comment&desc=desc&sn=off&ss=on&sc=on&keyword=&no=3167&category='>http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_QnA_etc&page=1&page_num=35&select_arrange=last_comment&desc=desc&sn=off&ss=on&sc=on&keyword=&no=3167&category=</a> </pre> <br /> 질문의 내용은, 닷넷 GC가 순환 참조를 해제할 수 있느냐입니다. 사실, 이 문제는 WeakReference를 이용해 간단하게 테스트 해볼 수 있습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; namespace circular_ref { class Program { static void Main(string[] args) { WeakReference wrA = null; WeakReference wrB = null; CallCrossRef(ref wrA, ref wrB); Console.WriteLine((wrA.Target as RefA)._a); Console.WriteLine((wrB.Target as RefB)._b); } private static void CallCrossRef(ref WeakReference wrA, ref WeakReference wrB) { <span style='color: blue; font-weight: bold'> RefA a = new RefA(); RefB b = new RefB();</span> wrA = new WeakReference(a); wrB = new WeakReference(b); <span style='color: blue; font-weight: bold'> a._instanceB = b; a._a = 5; b._instanceA = a; b._b = 6;</span> } } <span style='color: blue; font-weight: bold'> public class RefA { public RefB _instanceB; public int _a; } public class RefB { public RefA _instanceA; public int _b; }</span> } </pre> <br /> 서로 순환 참조하고 있고 위의 결과를 실행하면 5와 6값이 화면에 출력됩니다.<br /> <br /> 하지만, 다음과 같이 GC.Collect를 한번 해주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { WeakReference wrA = null; WeakReference wrB = null; CallCrossRef(ref wrA, ref wrB); <span style='color: blue; font-weight: bold'>GC.Collect();</span> Console.WriteLine((wrA.Target as RefA)._a); Console.WriteLine((wrB.Target as RefB)._b); } </pre> <br /> GC가 동작하고 순환참조임에도 불구하고 정상적으로 RefA a, RefB b 인스턴스가 해제된 것을 확인할 수 있습니다.<br /> <br /> GC의 동작과 관련해서는 card-table 개념과 함께 소개해 드렸던 링크에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET GC - 하위 세대의 객체를 포함하는 상위 세대의 참조를 추적하기 위한 card-table ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1670'>http://www.sysnet.pe.kr/2/0/1670</a> </pre> <br /> 마이크로소프트 측 직원이 아주 자세하게 설명해 주고 있으니 참고하시는 것도 좋겠습니다. ^^<br /> <br /> <ol> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-memory-allocation-a-walk-down-the-history'>Memory allocation, a walk down the history</a></li> <li>Why use garbage collection</li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-reference-counting-garbage-collection'>Reference Counting Garbage Collection</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-mark-and-sweep-garbage-collection'>Mark-sweep garbage collection</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-copying-garbage-collection'>Copying garbage collection</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-optimizing-reference-counting-garbage-collection'>Optimizing reference counting garbage collection</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-handling-overflow-in-mark-stage'>Handling overflow in mark stage</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-generational-garbage-collection'>Generational Garbage Collection</a></li> <li><a target='tab' href='https://docs.microsoft.com/en-us/archive/blogs/abhinaba/back-to-basics-how-does-the-gc-find-object-references'>How does the GC find object references</a></li> </ol> <br /> <hr style='width: 50%' /><br /> <br /> 추가적으로! 참조로 인한 메모리 릭이 발생할 수 있는 전형적인 사례가 하나 있는데 바로 "이벤트"입니다. 테스트를 위해 다음과 같이 코드를 만들어 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; namespace circular_ref { class Program { static void Main(string[] args) { <span style='color: blue; font-weight: bold'> CallEventFire(); GC.Collect(); Console.ReadLine();</span> } private static void CallEventFire() { EventPublisher publisher = new EventPublisher(); EventSubscriber subscriber = new EventSubscriber(); <span style='color: blue; font-weight: bold'>publisher.Fire += subscriber.DoFire;</span> } } public class EventPublisher { public delegate void FireEvent(); public event FireEvent Fire; protected void OnFire() { if (Fire != null) { Fire(); } } <span style='color: blue; font-weight: bold'> ~EventPublisher() { Console.WriteLine("~EventPublisher.Called()"); }</span> } public class EventSubscriber { public void DoFire() { } <span style='color: blue; font-weight: bold'> ~EventSubscriber() { Console.WriteLine("~EventSubscriber.Called()"); }</span> } } </pre> <br /> GC.Collect 이후 정상적으로 2개의 소멸자가 모두 호출되는 것을 볼 수 있습니다. 그런데 이 상태에서 "EventPublisher publisher = new EventPublisher();"의 코드를 static으로 빼면 어떻게 될까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static EventPublisher publisher = new EventPublisher(); private static void CallEventFire() { // EventPublisher publisher = new EventPublisher(); EventSubscriber subscriber = new EventSubscriber(); publisher.Fire += subscriber.DoFire; } </pre> <br /> 얼핏 보면, publisher 인스턴스는 static 루트 객체가 있으니 소멸되지 않겠지만 subscriber 인스턴스는 범위를 벗어났으니 힙에서 제거되어야 합니다.<br /> <br /> 하지만, 아무런 소멸자도 호출되지 않습니다. 왜냐하면 이벤트 구독 자체가 대상 객체를 참조하기 때문입니다. 이런 일이 발생하는 흔한 경우가 바로 이벤트 구독이 남발하는 윈도우 폼 응용 프로그램입니다. Form 위에서 동적으로 컨트롤을 생성/삭제하는 경우 그 컨트롤에 이벤트 핸들러를 걸어 두면 객체가 힙에 쌓이게 됩니다. (다행히, 대부분의 윈도우 폼 응용 프로그램은 사용자가 필요 없을 때 종료시키기 때문에 메모리 릭 문제에서 비교적 자유롭습니다.)<br /> <br /> 물론, 해결 방법은 그냥 필요 없어졌을 때 이벤트 구독을 해제하면 됩니다. 위의 예제에서는 다음과 같이 추가해 주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void CallEventFire() { // EventPublisher publisher = new EventPublisher(); EventSubscriber subscriber = new EventSubscriber(); publisher.Fire += subscriber.DoFire; <span style='color: blue; font-weight: bold'>publisher.Fire -= subscriber.DoFire;</span> } </pre> <br /> 이렇게 하고 나서 다시 실행해 보면, GC.Collect 호출에서 "~EventSubscriber.Called()" 출력을 볼 수 있습니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=877&boardid=331301885'>첨부 파일은 위의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7560
(왼쪽의 숫자를 입력해야 합니다.)