약한 참조(Weak Reference)와 메모리 관리
약한 참조(Weak Reference)는 가비지 컬렉션(GC)이 객체를 수집할 수 있도록 하면서도 객체에 대한 참조를 유지할 수 있는 기능입니다. 이를 활용하면 메모리 누수를 방지하고 메모리 사용량을 최적화할 수 있습니다. 이번 글에서는 약한 참조의 개념과 사용 방법, 그리고 메모리 관리에 어떻게 도움이 되는지 알아보겠습니다.
약한 참조의 개념
강한 참조(Strong Reference)
일반적으로 객체를 생성하고 변수가 이를 참조하면, 해당 객체는 강한 참조로 간주됩니다. 강한 참조를 가진 객체는 프로그램에서 참조하는 한 GC에 의해 수집되지 않습니다.
// 강한 참조 예시
MyClass obj = new MyClass();
위 코드에서 obj
는 MyClass
객체에 대한 강한 참조를 가지고 있습니다.
약한 참조(Weak Reference)
약한 참조는 객체의 수명에 영향을 주지 않는 참조입니다. 객체에 대한 약한 참조만 남아 있고 강한 참조가 없으면, GC는 해당 객체를 수집할 수 있습니다.
// 약한 참조 생성 예시
MyClass obj = new MyClass();
WeakReference<MyClass> weakRef = new WeakReference<MyClass>(obj);
obj = null; // 강한 참조 해제
위 코드에서 obj
에 대한 강한 참조를 해제하면, weakRef
만 남게 되며, 이때 객체는 GC에 의해 수집될 수 있습니다.
약한 참조의 사용 사례
캐시 구현
캐시에서 객체를 보관할 때 약한 참조를 사용하면 메모리 누수를 방지할 수 있습니다. 필요하지 않은 객체는 GC에 의해 자동으로 수집됩니다.
// 약한 참조를 이용한 캐시 예시
class Cache
{
private Dictionary<string, WeakReference<MyClass>> _cache = new Dictionary<string, WeakReference<MyClass>>();
public void Add(string key, MyClass value)
{
_cache[key] = new WeakReference<MyClass>(value);
}
public MyClass Get(string key)
{
if (_cache.TryGetValue(key, out WeakReference<MyClass> weakRef))
{
if (weakRef.TryGetTarget(out MyClass target))
{
return target;
}
else
{
// 객체가 수집된 경우 처리
return null;
}
}
return null;
}
}
이벤트 핸들러에서의 메모리 누수 방지
이벤트 핸들러에 강한 참조를 사용하면 구독자가 해제되지 않아 메모리 누수가 발생할 수 있습니다. 약한 이벤트 패턴을 사용하여 이를 방지할 수 있습니다.
// 약한 이벤트 패턴 구현 예시
class EventPublisher
{
public event EventHandler MyEvent;
public void RaiseEvent()
{
MyEvent?.Invoke(this, EventArgs.Empty);
}
}
class EventSubscriber
{
public EventSubscriber(EventPublisher publisher)
{
WeakEventManager<EventPublisher, EventArgs>.AddHandler(publisher, nameof(EventPublisher.MyEvent), OnEvent);
}
private void OnEvent(object sender, EventArgs e)
{
// 이벤트 처리 로직
}
}
약한 참조 사용 방법
WeakReference 클래스
.NET에서는 WeakReference
및 제네릭 버전인 WeakReference<T>
클래스를 제공합니다.
// WeakReference 사용 예시
MyClass obj = new MyClass();
WeakReference weakRef = new WeakReference(obj);
obj = null; // 강한 참조 해제
if (weakRef.IsAlive)
{
MyClass target = (MyClass)weakRef.Target;
// 객체 사용
}
else
{
// 객체가 수집됨
}
WeakReference<T>
클래스
WeakReference<T>
는 제네릭 타입을 지원하며, TryGetTarget
메서드를 통해 객체를 안전하게 가져올 수 있습니다.
// WeakReference<T> 사용 예시
MyClass obj = new MyClass();
WeakReference<MyClass> weakRef = new WeakReference<MyClass>(obj);
obj = null; // 강한 참조 해제
if (weakRef.TryGetTarget(out MyClass target))
{
// 객체 사용
}
else
{
// 객체가 수집됨
}
주의 사항
객체의 수집 시점 불확실성
약한 참조를 사용하는 객체는 언제든지 GC에 의해 수집될 수 있으므로, 항상 존재한다고 가정하면 안 됩니다.
다중 스레드 환경에서의 동기화
멀티스레드 환경에서는 약한 참조를 사용할 때 동기화에 주의해야 합니다. 객체의 존재 여부를 확인한 후 사용하기까지의 사이에 객체가 수집될 수 있습니다.
// 동기화 필요 예시
if (weakRef.TryGetTarget(out MyClass target))
{
lock (target)
{
// 객체 사용 중에도 수집될 수 있으므로 주의
}
}
강한 참조로의 승격
약한 참조로부터 객체를 가져와 강한 참조를 생성하면, 해당 객체는 GC에 의해 수집되지 않습니다.
// 강한 참조로 승격 예시
if (weakRef.TryGetTarget(out MyClass target))
{
// 강한 참조 생성
MyClass strongRef = target;
// 이제 객체는 수집되지 않음
}
약한 참조를 활용한 메모리 최적화
메모리 사용량 감소
약한 참조를 사용하면 불필요한 객체가 메모리에 남아 있지 않도록 하여 전체 메모리 사용량을 감소시킬 수 있습니다.
메모리 누수 방지
객체의 수명 주기를 명시적으로 관리하지 않아도, 약한 참조를 통해 메모리 누수를 방지할 수 있습니다.
결론
약한 참조는 객체에 대한 참조를 유지하면서도 GC가 객체를 수집할 수 있도록 허용하는 유용한 기능입니다. 이를 활용하여 캐시 구현이나 이벤트 핸들러의 메모리 누수 방지 등 메모리 관리에 도움을 줄 수 있습니다. 그러나 약한 참조를 사용할 때는 객체의 수집 시점이 불확실하다는 점과 멀티스레드 환경에서의 동기화에 주의해야 합니다.