성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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'>null 처리된 객체가 왜 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# WeakReference이 CPU 플랫폼 설정마다 결과가 틀리게 나옵니다. ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/4702'>http://www.sysnet.pe.kr/3/0/4702</a> </pre> <br /> 문제를 정리해 보면, 다음과 같이 코딩을 한 경우 (제 환경에서) Debug 빌드로 하면 Target이 살아 있는 걸로 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET 4.5.2 + Debug 빌드 테스트 (x86/x64) public class Man { public string Name { get; private set; } public Man(string name) { Name = name; } } class Program { static void Main(string[] args) { TestWeakReference(); Console.ReadKey(); } private static void TestWeakReference() { <span style='color: blue; font-weight: bold'>Man m</span> = new Man("A"); <span style='color: blue; font-weight: bold'>WeakReference refMan</span> = new WeakReference(m); <span style='color: blue; font-weight: bold'>m = null;</span> GC.Collect(); Console.WriteLine("약한 참조 원본: {0}", (m == null ? "null" : m.Name)); Console.WriteLine("약한 참조 참조: {0}", (refMan.Target == null ? "null" : (refMan.Target as Man).Name)); } } // 출력 결과 약한 참조 원본: null <span style='color: blue; font-weight: bold'>약한 참조 참조: A</span> </pre> <br /> 코드 상으로 보면 분명 "m = null"로 되었고 GC.Collect가 수행되었으니 당연히 WeakReference의 Target이 null이 나와야 합니다. 그런데, 왜 여전히 살아있을까요?<br /> <br /> 사실, 제가 답변에도 썼지만 마이크로소프트 입장에서는 WeakReference의 Target이 정확히 언제 null로 된다거나, 심지어 힙 객체가 '정해진 시점'에 해제된다고 명시하고 있진 않습니다. 즉, 그 시점을 정확히 예측해서 어떤 부가적인 코드를 작성하는 것은 바람직하지 않습니다.<br /> <br /> 그래도, 저런 결과가 나오면 좀 이상하긴 할 텐데요. 어디... 그 원인을 한번 밝혀 볼까요? ^^<br /> <br /> 일단, 우리가 배운 바로는 GC가 되려면 또 다른 참조 객체, 스택 및 레지스터(CPU Register)에 그 참조가 없어야 한다는 것입니다. 그렇다면, 저 경우에 분명히 그 세 가지 중의 하나는 어디에선가 참조값이 있어야 합니다.<br /> <br /> 그중에서 우선 "또 다른 참조 객체"는 후보가 아닙니다. 코드상에 WeakReference가 'm'의 참조값을 가지고는 있지만 GC에게 Weak 참조는 GC 대상에서 고려하지 않기 때문에 무시해도 됩니다. 나머지 후보라면 스택 및 레지스터가 있는데요. 이를 살펴보기 위해 다음과 같이 소스 코드에 Console.ReadKey()를 추가하고,<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 TestWeakReference() { Man m = new Man("A"); WeakReference refMan = new WeakReference(m); <span style='color: blue; font-weight: bold'>Console.ReadKey();</span> m = null; GC.Collect(); Console.WriteLine("약한 참조 원본: {0}", (m == null ? "null" : m.Name)); Console.WriteLine("약한 참조 참조: {0}", (refMan.Target == null ? "null" : (refMan.Target as Man).Name)); Console.ReadKey(); } </pre> <br /> 실행한 후 Visual Studio를 이용해 "Attach to Process..."로 연결한 다음 저 상태에서의 기계어 코드를 확인해 봅니다. 그럼, 대략 다음과 같은 화면을 볼 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='gc_weak_ref_1.png' src='/SysWebRes/bbs/gc_weak_ref_1.png' /><br /> <br /> 여기서, 생성된 m에 대한 인스턴스를 보관하고 있는 곳은 2군데입니다.<br /> <br /> 하나는 가장 상단의 "call 00007FFDDCB24C10"로 표현된 Man 객체의 생성자 호출에 이은 "mov qword ptr [rbp-40h], rax"로 "[rbp-40h]" 스택에 보관되어 있고, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007FFD7D4D0510 E8 FB 46 65 5F call 00007FFDDCB24C10 00007FFD7D4D0515 48 89 45 C0 mov qword ptr <span style='color: blue; font-weight: bold'>[rbp-40h]</span>,rax </pre> <br /> 또 하나는 [rbp-40h]의 값을 다시 [rbp-30h]로 이동해서 보관해 놓은 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007FFD7D4D052F 48 8B 4D C0 mov rcx,qword ptr [rbp-40h] 00007FFD7D4D0533 48 89 4D D0 mov qword ptr <span style='color: blue; font-weight: bold'>[rbp-30h]</span>,rcx </pre> <br /> 반면 하단에서 "m = null" 할 때는 [rbp-30h] 값만 0으로 대입하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > m = null; 00007FFD7D4D0569 33 C0 xor eax,eax 00007FFD7D4D056B 48 89 45 D0 mov qword ptr <span style='color: blue; font-weight: bold'>[rbp-30h]</span>,rax </pre> <br /> 실제로 [rbp-40h]의 값과 [rbp-30h]의 값이 같다는 것을 메모리 창을 통해서 확인할 수 있습니다. 레지스터 창을 통해 RBP == 0000006B335FEE10로 나오니까, 각각 다음과 같이 계산되고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [rbp-40h] == 0x0000006B335FEDE0 [rbp-30h] == 0x0000006B335FEDD0 </pre> <br /> 이에 대한 메모리 값을 확인해 보면 "e8 45 a4 40 5a 02 00 00"로 동일하게 나옵니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='gc_weak_ref_2.png' src='/SysWebRes/bbs/gc_weak_ref_2.png' /><br /> <br /> 따라서, GC는 [rbp-30h]의 참조가 없어졌다 해도 [rbp-40h]의 값으로 인해 해당 객체를 제거하지 못했던 것입니다.<br /> <br /> 어디... 그럼 실험으로 증명해 볼까요? ^^ <br /> <br /> 해당 스택의 값을 비주얼 스튜디오의 메모리 창을 통해 임의로 '00 00 00 00 00 00 00 00'으로 초기화할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='gc_weak_ref_3.png' src='/SysWebRes/bbs/gc_weak_ref_3.png' /><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;' > 약한 참조 원본: null 약한 참조 참조: null </pre> <br /> 그러니까, 결국 JIT 컴파일러가 생성해 낸 기계어 코드에서 Debug 빌드인 경우 스택에 두 번 보관해 놓은 값 때문에 저런 문제(?)가 발생한 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 때문에 Release 빌드로 하면 이런 문제가 발생하지 않습니다. 릴리스 빌드 시 JIT 컴파일러는 최적화된 코드를 생성하기 때문에 쓸데없는 스택 낭비를 유발하는 코드를 생성하지 않기 때문입니다.<br /> <br /> 이 외에도 코드를 다음과 같이 바꿔주면 Debug 빌드 시에도 약한 참조가 끊기게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET 4.5.2 + Debug/Release 빌드 테스트 (x86/x64) class Program { static void Main(string[] args) { <span style='color: blue; font-weight: bold'>WeakReference refMan = TestWeakReference(); GC.Collect();</span> Console.WriteLine("약한 참조 참조: {0}", (refMan.Target == null ? "null" : (refMan.Target as Man).Name)); Console.ReadKey(); } private static WeakReference TestWeakReference() { Man m = new Man("A"); WeakReference refMan = new WeakReference(m); <span style='color: blue; font-weight: bold'>m = null;</span> Console.WriteLine("약한 참조 원본: {0}", (m == null ? "null" : m.Name)); return refMan; } } // 출력 결과 약한 참조 원본: null 약한 참조 참조: null </pre> <br /> 이유를 아시겠죠? ^^ 이런 경우 디버그 빌드로 해도 스택에 2중으로 보관되어 있던 값이 메서드가 리턴하면서 스택이 해제되기 때문에 CLR의 GC 구성 요소가 검사해야 할 영역에서 제외되기 때문입니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=998&boardid=331301885'>첨부한 코드는 이 글의 예제를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2107
(왼쪽의 숫자를 입력해야 합니다.)