필자가 지금까지 여러 차례의 포스트에서 닷넷 CLR의 가비지 컬렉션을 설명하고 Finalizer 가 무엇이며 어떻게 활용할 것이고 문제는 무엇인지 설명하였고 그에 대한 대안으로 Dispose 패턴을 설명하였다. 그리고 그 최종(?) 포스트일지도 모를 이 글을 쓰면서 Dispose 패턴에 대해 정리를 하고자 한다. 지금까지 앞서 글들을 안 읽어 보았다면 초간단 요약본이라도 쓰바 읽어 보기 바란다. 쩌는 귀차니즘을 극복하고 관련 글들에 대한 링크도 졸라 달아 놨으니 심심하면 링크 클릭해서 상세한 내용을 읽어 봐도 좋다. (젭알 읽어 보길……) 닷넷 CRL은 보다 빠르며 개발자에게 부담을 주지 않고 효율적으로 메모리를 관리하기 위해 가비지 컬렉션 메커니즘을 사용한다. 가비지 컬렉션 메커니즘은 메모리를 빠르게 할당할 수 있으며 메모리 회수를 CLR이 담당하므로 개발자가 특별히 메모리 회수에 신경쓰지 않아도 된다는 장점을 가지고 있다. 닷넷 CLR은 메모리 활용도를 최대로 올리기 위해 세대별 가비지 컬렉션 기법이나 LOH, 다양한 가비지 컬렉션 모드 와 같은 최적화를 사용하고 있지만가비지 컬렉션이 발생하는 시점이 불분명하다는 문제를 가지고 있습니다. 이는 파일이나 데이터베이스 연결과 같이 시스템의 중요한 자원들의 해제 시점이 불분명 하다는 것이며 귀중한 시스템 자원이 더 이상 사용 되고 있지 않음에도 불구하고 아직 해제가 안 된 채로 가비지 컬렉션이 발생할 때까지 남아 있을 수 있음을 의미한다. 따라서 닷넷 프레임워크는 IDispose 인터페이스를 사용하여 시스템 자원을 포함하는 객체는 사용이 끝나면 Dispose 메서드 호출을 통해 자원을 즉시 해제할 수 있도록 하는 것이다(일부 클래스는 Dispose 대신 Close 메서드를 제공하거나 Dispose 메서드와 Close 메서드를 모두 제공하기도 한다). 모든 개발자가 using 키워드를 사용하거나 try~finally 블럭에서 해제가 필요한 객체(Connection 객체, FileStream 객체 등등)를 해제를 해준다면야 아무런 문제가 없을 것이다. 다양한 이유(실수, 날림코드, 음주 코딩, 닭대가리, 븅신 등등)에서 개발자가 Dispose 혹은 Close 메서드를 호출하지 않았을 때의 대비도 해야만 한다. Finalizer 는 CLR에서 제공하는 특수한 메서드로써 이 메서드를 포함하는 객체가 가비지 컬렉션의 대상이 되면 이 객체에 대해 Finalize 메서드를 호출한 후에 객체를 메모리에서 제거하도록 되어 있다. 따라서 IDispose 인터페이스를 구현하는 타입이라 할지라도 개발자가 Dispose 메서드를 호출하지 않은 경우, Finalizer 에서 자원을 해제해 줌으로써 자원 누수(resource leak)를 막을 수 있다. 하지만 CLR이 객체들의 Finalize 메서드를 호출하는 순서가 정해져 있지 않기 때문에 중복으로 Dispose가 호출되는 상황이 올 수도 있다. 이 상황은 Finalizer 사용시 주의 해야 할 사항으로써 지난 포스트에서 이미 다룬 바가 있다. 결론적으로 Dispose 패턴을 다루었던 지난 포스트에서 설명한 대로 IDispose 인터페이스를 구현할 때에는 명시적으로 Dispose 메서드 호출에 의한 자원 해제인지 아니면 Finalizer에 의한 자원 해제 인지를 구별이 필요하며 그에 따른 적절한 처리가 필요하다. 다음 코드는 전형적인 Dispose 패턴 코드를 보여준다. SafeType 클래스가 내부적으로 사용하는 자원(StreamWriter)을 해제해주기 위해 IDispose 인터페이스를 구현하였고 Finalizer 역시 구현하였다. Dispose 메서드가 명시적으로 호출되면 자원을 해제한 후 이미 자원을 해제 했으므로 가비지 컬렉터가 Finalizer를 호출하지 않도록 한다(SuppressFinalize 메서드 호출). 한편, Dispose 메서드가 명시적으로 호출되지 않고 Finalize 메서드가 호출된 경우에는 자원을 해제하지 않는다. 그 이유는 SafeType 클래스가 사용하는 자원(StreamWriter)이 관리되는 자원이기 때문에 이미 이 관리되는 자원이 가비지 컬렉터에 의해 해제되었을 수도 있기 때문이다. 상세한 내용은Finalizer에 관련된 글을 참고하기 바란다. 1: class SafeType : IDisposable 2: { 3: private StreamWriter _stream; 4: 5: public SafeType() 6: { 7: _stream = new StreamWriter("Test.txt"); 8: } 9: 10: ~SafeType() 11: { 12: Dispose(false) 13: } 14: 15: public void Dispose() 16: { 17: Dispose(true); 18: GC.SuppressFinalize(); 19: } 20: 21: protected virtual void Dispose(bool disposing) 22: { 23: if (disposing) 24: { 25: _stream.Dispose(); 26: } 27: } 28: 29: public void DoSomething() 30: { 31: ... 생략 ... 32: } 33: } |