안녕하세요 삽잡이입니다.
이번 시간에는 비관리 리소스를 종결 시키는 과정에 대한 이론 공부를 해보고자합니다.
지금 배우는 이 비관리 리소스, 성능에 대해서 알기 위해
물론, 프로그램을 하면서 사실 큰 영향이 있을지는 모르겠으나
이론적으로 갖춰야할 내용입니다.
시작하겠습니다.
루트가 없는 객체는 가비지 컬렉션 대상이 된다고합니다.
이는 '관리된 힙에 생성된 객체에 대한 참조를 갖고 있는 저장소의 위치'를 말합니다.
루트가 없다면 객체가 덩그라니 붕 떠있게 되는데,
이때 가비지 컬렉션 대상이 된다 이겁니다.
루트가 없다면 바로 객체가 날라갈까요? 아닙니다.
가비지 컬렉터가 동작하기 전에는 남아있습니다.
예를 들어서 메모리가 4GB인데,
만약, 1~2MB 정도 사용했다고 해봅시다...
이 용량에 객체들이 있는데 만약 루트를 잃는다면, 제거할 필요가 있을까요?
용량이 남아도는데...
이 때 가비지 컬렉터를 동작시키는 것이 오히려 더 비효율적입니다...
가비지 컬렉터는 루트가 없다고 무조건 동작시키는 것이 아닙니다.
불필요한 CPU를 낭비할 필요 없이
메모리가 부족할 때에 삭제할 대상으로 표시해둔 객체들이
가비지 컬렉터에의해 제거가 되는 것입니다.
객체를 할당하게 되면 CLR이 관리하는 Heap 영역에서 관리를 당합니다(?)..
이 객체는 우리는 리소스 즉, 자원이라고 부릅니다.
메모리도 자원이니깐요...
리소스는 시스템에게 얻어서 사용하고 다시 반납해야하는
즉, 시스템의 것이라는 의미를 가지고 있습니다.
시스템에게 할당 받아 여럿이서 같이 사용할 수 있다 이겁니다.
그래서 자원 및 리소스라고 부르는 것이지요...
아무튼... 그런데 아무리 닷넷에서는
CLR을 통해 어플리케이션들이 관리된다고 하더라도
가끔은 OS가 제공하는 자원들을 사용할 때가 있습니다.
어플리케이션에서 OS의 자원을 쓰고자한다면?
CLR에 있는 자원들은 사용한 후에 그냥 냅둬도 알아서 CLR이 마무리를 해주는데,
OS의 자원은 즉, 닷넷에 관리 자원이 아닌 자원들은
우리가 '직접' 제거를 해줘야합니다.
일반적으로 객체가 생성될 때 자원이 할당됩니다.
객체를 사용중에 할당되지 않겠습니까...
그러면 자원은 언제 제거할까요?
C++에서는 소멸자에서 일반적으로 제거를 해줬습니다.
그런데 C#에서는 소멸자에서 제거를 하긴 하나 C++과는 차이가 있습니다.
C++은 소멸자가 호출되는 시점이 명확합니다.
소멸자는 객체가 소멸될 때에 마무리 작업을 위해 호출됩니다.
(참고로, 생성자는 객체가 생성될 때 초기화 작업을 위해 호출되지요...)
C++에 객체는 소멸 시점이 명확하지요
new 객체는 delete를 할 경우에, stack 메모리는 블럭 영역을 벗어날 시에,
정적객체는 프로그램 종료시에...
즉, 명확하기 때문에 '그 때'에 리소스를 제거하면 되지만,
C#의 닷넷 객체는 아닙니다...
C#의 닷넷 객체는 소멸자가 언제 호출될지 모릅니다.
아까의 예시를 통해 말씀드리지 않았습니까?
다시 말씀드리지만,
아무리 객체를 사용하지 않더라도 즉, 루트가 존재하지 않더라도
객체가 소멸되는 시점을 명확하게 말할 수 없다 이겁니다.
이 뜻은 객체의 소멸자가 호출되지 않고,
소멸자에서 닫는 리소스는 아직도 닫지 못한다는 소리가 됩니다.
CLR에서 관리되는 Heap의 객체들이 있다고 해보겠습니다.
이 객체들은 닷넷이 제거하기때문에 상관이 없지만,
OS의 자원을 가지고 노는 객체들은 닷넷에서 결정을 못합니다.
따라서 OS 자원을 가지고 있는 객체가 제거될 때에 제거하라는 명령을 해줘야합니다.
그런데... 그 시점을 알 수 없다 이겁니다... OTL
OS에 있는 자원도 그 누군가에게 사용될 수 있습니다.
따라서 다 사용을 했다면 분명 반환을 해줘야합니다...
이때! '종결'을 사용하는 것입니다.
기억하실진 모르겠지만...
object 클래스에서는 finalize() 메서드를 제공해줬습니다.
그런데 이 finalize() 메서드는 직접 오버라이드를 할 수 없습니다.
이유인 즉, 소멸자를 만들면 오버라이드가 되기 때문입니다.
소멸자가 만들어지면 종결 작업을 해야한 다는 것을 명시하게 되지요...
닷넷의 리소스가 아닌 OS의 리소스(비관리 리소스)들을 종결하고자할 때에
즉, 마무리 처리를 하고자할 때에, 그 필요가 있을 때에
소멸자를 만든다고 생각하면 됩니다.
그런데, 수 많은 객체들 중에서 소멸자를 가지고 있는 객체들이 얼마나 될까요?
객체들이 사라질 때마다
소멸자 를 가지고 있는 객체를 파악하면 불필요한 성능을 낭비하지 않겠습니까?
따라서, 소멸자가 필요한 객체들은
CLR의 종결자 큐라는 곳에 소멸자를 등록을 하게됩니다.
그렇게되면, 일반 객체들이 소멸될 때에는 그냥 소멸 시키지만,
그렇지 않은 객체들은 등록되어있는 소멸자를 호출하게 됩니다.
모든 객체가 종결자 큐에 올라가는 것이 아니라,
소멸자가 있는 객체들만 올라가는 것이고,
이 객체들이 종결될 때에 큐에 등록되어있는 소멸자가 호출된다는 것입니다.
소멸자에다가 비관리 리소스를 종료시키라고 했더라도,
이 소멸자는 언제 호출될지 모릅니다...
그렇다면... 더 이상 객체를 사용하지 않는다는 사실을 알고 있을 시
굳이 소멸자가 호출될 때까지 기다릴 필요가 있을까요?
즉, 비관리 리소스를 수동으로 직접 반납할 수 있다 이겁니다.
그런데 사람은 망각의 동물이라고 하지요...
직접하지 않고 까먹을 수가 있습니다... 따라서 방어막을 설치해둡니다.
뭔 Dog Sound냐면...
사용자가 직접 해제하는 것을 잊어벼렀어도
최소한 소멸자에서 호출할 수 있도록 한다 이겁니다...
비관리 리소스를 사용할 때에는
수동으로 사용자가 반납하도록 만들어 놓고,
혹시 잊어버렸을 최악의 경우에는 소멸자가 반납하도록 만들어 놓습니다.
소멸자 큐에 소멸자를 등록하는 이유는
비관리 리소스를 반납하기 위한 소멸자를 호출시키기 위해서입니다.
그런데 만약 수동으로 리소스를 반납했다면 큐에서 제거해줘야하지 않겠습니까?
왜냐하면, 성능도 떨어질 것이고 또 반납할 시도를 하기 때문이지요...
자... 그런데 수동으로 반납을 한다고 해봅시다.
그런데 사용자마다 반납하는 방식이 다르다면... 문제가 생기겠지요...
왜냐하면 내가 구현한 코드를 나만 보는 것이 아닌,
다른 사람들도 보는 경우를 생각해보시길 바랍니다.
따라서 인터페이스를 통해 약속을 합니다.
즉, 클래스의 객체가 비관리 리소스를 사용하고 있는데
반납을 수동으로 할 경우에는 '~ 이런식으로 하자! '
이렇게 인터페이스를 통해 약속을 했단 말입니다...
누구든 public interface IDiposable() 인터페이스를 보고
알 수 있도록 말입니다...
따라서 이 곳에 수동으로 반납할 기능을 추가하면 되겠군요...
그리고 finally 블럭에다가 dispose() 호출을 해줘야합니다.
뭔 일이 있어도 리소스를 반납해줘야하기 때문입니다...
그런데 이 것이 귀찮다면...
using 키워드를 사용하면 됩니다.
이 using 키워드는 namespace에서 사용하는 using이 아닙니다...
여기서 사용하는 using 키워드는 괄호가 있지요...
using의 유효범위가 끝나면 자동으로 Dipose() 메서드가 호출됩니다.
그런데 사실 이 using 키워드도 내부적으로는 try- catch 문으로 이루어져 있습니다..
컴파일러가 자동으로 작성해준다? 이 정도로 생각하시면 됩니다...
아무튼... dispose()를 호출하는 방법에는
finally와 using 키워드 이 두가지가 있다는 것입니다...
지금까지 내용을 정리해보자면,
종결 처리를 하기 위해서는 약속된 interface를 사용해야하고,
dipose() 메서드를 통해 비관리 리소스를 반납하는 기능을 구현하자고 했습니다.
dispose() 메서드를 통해 비관리 리소스를 반납하게 됬다면
소멸자 큐에서 빼줘야하지 않겠습니까?
System에 있는 GC 클래스에는
소멸자 큐에서 소멸자를 빼주는 메서드를 제공해줍니다.
즉, 이미 리소스를 반납했으니 또 호출할 필요가 없기에 종결 큐에서 빼는 것입니다.
MS에서는 이 리소스 종결에 대해서 안정적이고 효율적인 패턴을 제공해줍니다.
뭐... MSDN을 확인해보시길 바랍니다...
굳이 MS에서 제공해주는 패턴이 있는데도 이렇게 주구장창 설명한 이유는..
알고 작성하는 것과 모르고 작성하는 것은 다르니깐요!
푸하하하
이번 시간은 여기까지 하도록 하겠습니다.
이상 삽잡이였습니다!
'삽질의 현장 > - .NET' 카테고리의 다른 글
#041_닷넷(.NET)_.Net Framework 기본 - 인터페이스 Intro (0) | 2015.10.29 |
---|---|
#040_닷넷(.NET)_.Net Framework 기본 - 세대 (0) | 2015.10.29 |
#038_닷넷(.NET)_.Net Framework 기본 - 예외 처리 (0) | 2015.10.29 |
[삽잡이::C#]문자열 (0) | 2015.10.29 |
#037_닷넷(.NET)_.Net Framework 기본 -Object 메서드 (static) (0) | 2015.10.29 |