System.WeakReference 는 왜 그리고 언제 사용하는 건가요?

제목 그대로 WeakReference 라는 클래스가 궁금하네요. 개발을 하다보면 .NET 소스 코드에 자주 등장하는데 굳이 사용하는 이유를 모르겠네요. 언제 GC 당할지 모르는 참조를 사용하면 성능이 향상되니까 사용하는 것 같아보이는데, 구체적으로 어떤 원리로 성능을 향상시키는 건지 알고 싶습니다.

// Xamarin.Forms.BindableLayout 소스코드 일부입니다.
View CreateEmptyView(object emptyView, DataTemplate dataTemplate)
{
    readonly WeakReference<Layout<View>> _layoutWeakReference;
    // ...
    public BindableLayoutController(Layout<View> layout)
    {
        _layoutWeakReference = new WeakReference<Layout<View>>(layout);
    }
    // ...
    if (!_layoutWeakReference.TryGetTarget(out Layout<View> layout))
    {
        return null;
    }

    if (dataTemplate != null)
    {
        var view = (View)dataTemplate.CreateContent();
        view.BindingContext = layout.BindingContext;
        return view;
    }
    // ...
}

위 .NET 코드처럼 주로 특정 클래스에 UI 작업을 할 때 많이 등장하는데, WeakReference 가 아니라 실제 참조를 가지고 있으면 UI 객체가 GC 가 되지 않으니, UI 객체가 GC 될 수 있도록 WeakReference 를 사용하는게 아닐까 추측하고 있습니다.

이외에도 사용하는 부분들이 보이는데 어떤 목적과 이점이 있어서 사용하는건지 알고 싶습니다.

혹시 참조할 만한 공식 문서를 알고 계시다면, 댓글로 링크를 남겨주시면 감사하겠습니다.

좋아요 2

제가 알기로 WeakReference는 주소 캐싱용도로 사용한다고 알고 있습니다.
아시다시피 C#에서 객체를 참조하면 레퍼런스 카운트가 올라가는데, WeakReference는 레퍼런스 카운트를 올리지 않고 객체를 참조 할 수 있어서, 이 객체가 GC에 의해 제거된건지 파악하는 용도로 사용한다고 알고 있습니다.

WeakReference의 IsAlive 속성을 통해 수집되었는지 여부를 체크하는 용도라고 기억하고 있습니다.

저는 고성능 닷넷 프로그래밍 책에서 봤습니다. 82 페이지에 있습니다.

좋아요 2

흐음… 그렇다면 주소를 캐싱하고 GC 에 의해 수집되었는지 확인하는게 왜 필요할까요?

좋아요 2

책에 써있는 문구를 그대로 쓰자면, (써도 되는지는 모르겠지만…문제가 있다면 삭제하겠습니다.)

약한 참조는 가비지 수집기에서 개체를 정리할 수 있게 해주는 개체에 대한 참조다. 반대는 (그 개체에 대해) 수집을 방지하는 강한 참조다. 약한 참조는 유지하고 싶은 비용이 높은 개체를 캐시하는데 주로 유용하지만, 충분한 메모리 압력이 있다면 참조를 풀어주려 한다.
WeakReference는 IsAlive 속성을 갖지만, 해당 개체가 살아 있는 경우가 아니라 죽은 경우인지 결정하는 데만 유용하다. IsAlive를 검사했는데 값이 true라면, IsAlive 속성을 검사한 후 해당 개체를 수집할 수 있는 가비지 수집기와 경쟁에 있는 것이다. 개체 참조를 소유한 강한 참조로 복사하고 거기서 확인하자.
WeakReference를 사용하는 아주 좋은 방법은 개체가 강한 참조에 잡혀 시작할 수 있는 캐시 부분이다. 이는 사용되지 않은 충분한 시간이 지난 후 개체가 강등되어 궁극적으로 사라질 수 있는 약한 참조가 될 수 있다.

요정도만 써있습니다.

좋아요 1

저도 책에서 본게 전부라 아직 실험해본적은 없어서…다른 좋은 분들이 더 많고 좋은 의견들을 주셨으면 좋겠습니다!!

좋아요 1

제가 이해한 것은 조금 다릅니다. 그런데 이게 맞는지는 아직 잘 모르겠어요.
아래 내용은 제가 이해한 내용으로 정확하지 않은 부분이 있을 수 있으니 참고 부탁합니다.
(저도 실제 사용해본 적이 없었기 때문에 이번 기회로 알게된/이해한 내용을 업무에 천천히 적용해볼까 합니다)

일단 GC의 가비지 수집을 막기 위한 캐싱은 적절하지 않은 것 같아요.

제가 이해한 WeakReference의 사용시점은 주로 UI 객체를 다룰때로, WPF 기준으로 설명드리자면 하나의 ViewModel이 있는 Control에서 다른 UI 객체에 접근할 때 필요할 것 같아요.

같은 View/ViewModel은 생명주기를 같이하기 때문에 문제없어 보이지만, 다른 UI 객체는 생명주기가 다를 확률이 높기 때문입니다.

예를들면 어떤 페이지(Some)의 View/ViewModel이 있고 그 ViewModel이 다른 페이지(Other)의 View를 참고해야 하는 상황이면 보통 아래와 같이 작성할 것 같은데요.

public class SomeViewModel
{
   protected OtherView OtherView { get; set; }
}

이 과정에서 OtherViewSomeViewModel에서 참조를 하나 하고 있기 때문에 정상적으로 소멸해야 할 시점에 소멸할 수 없게 됩니다.
그래서 OtherView의 참조 카운트가 오르지 않으면서 해당 객체를 사용할 수 있는 방법이 WeakReference 같아요.

public class SomeViewModel
{
   protected WeakReference<OtherView> OtherView { get; set; }
}

이렇게 사용할 경우 OtherView의 생명주기에 영향을 주지 않으면서 해당 객체를 활용할 수 있습니다
(단, WeakReference의 Target을 보관하고 있으면 안됩니다! 사용 시점에만 호출해서 사용해야 합니다).

그리고 이벤트 쪽도 사용할 때 주의가 필요하며 이를 활용할 수 있습니다.


마지막으로 맨 위에 언급한 캐싱 목적은 아래 페이지에도 언급이 되어 있듯이 단순 “메모리 관리 문제에 대한 자동 솔루션으로 Weak References를 사용하지 않습니다. 대신, 애플리케이션의 개체를 처리하기 위한 효과적인 캐싱 정책을 개발합니다.” 처럼 큰 메모리를 다루는 것은 별도 관리할 수 있는 유틸리티를 만드는 것이 좋을 것 같습니다.
이번에 검색하다가 지나가다 본 글로 IDisposable를 이용해서 만드는 것을 권장하는 걸 본 것 같아요.

좋아요 3

일단 제가 말씀드린 부분 중 오해를 수정드리자면, WeakReference를 적용한다고 해서 GC가 제거하지 못하는 것이 아닙니다. 말씀하신대로 레퍼런스 카운트가 증가되지 않기 때문에(저도 위에 댓글에서 레퍼런스 카운트가 올라가지 않는다고 언급했습니다.) 어차피 마크-스윕의 대상이 되는거고, 캐싱이라는 단어때문에 오해가 발생한 듯 한데, GC에서 제거 되었는지 확인만 하는 의도로서 참조 캐싱이라 말씀드린 것입니다. 책에도 그렇게 적혀있어서 고대로 썼더니 오해가 생겼네요…ㅎㅎ

제가 책에서 그대로 인용한 문구에도 보시면 해당 개체가 살아 있는 경우가 아니라 죽은 경우인지 결정하는데만 유용하다. 라고 써놨습니다. 따라서 GC가 되지 않는 것은 아닙니다.

따라서 말씀하신 부분과 제가 말하는 의도는 일치한다고 봅니다. 객체가 사라졌는지 확인한다는 의미에서요.

좋아요 2

아 제가말한 캐싱은 @김예건 님께서 많은 메모리를 차지하는 객체를 붙잡아 두는 용이라고 하신 부분을 말한 거에요…
저도 제대로 언급하지 않아서 오해가 생겼네요 ㅎㅎ

좋아요 2

아 그랬군요…ㅎㅎ 링크해주신 WPF에서 실제 약한 이벤트 참조패턴은 실제로 사용하는 방법을 보여주는 좋은 예시가 될 듯 합니다. 이론으로만 보던걸 실제로 써있는걸 보니 저도 적용하면 좋겠습니다.

링크 감사합니다!!

좋아요 2

@level120 저도 정확하게 설명하지 않아서 오해가 발생한거 같네요. 정확하게는 객체를 GC 가 컬랙트하지 못하도록 붙잡는다는 뜻은 아닙니다. 그럴 용도라면 약한 참조 자체를 사용할 필요가 없으니까요. 다만 약한 참조를 사용하게 된다면 그 의도가 GC 에게 수집되는 조건을 만족한 객체를 다시 강한 참조로 붙잡을 수 있도록 참조를 캐싱해놓는 용도가 아닐까하는 의미입니다. 메모리 관리보다는 객체가 생명을 다하기 전에 다시 한번 살아날 기회를 만드는 용도가 아닐까요?

첨부해주신 '약한 이벤트 패턴’은 현재 진행하는 프로젝트에 유용하게 사용할 수 있을거 같습니다. 감사합니다. ㅎㅎ

좋아요 3

저도 좀… 의견을 드리자면…

WeakReference는 "나는 너의 생명주기에는 관여하지 않을거야. 그냥 너가 살아 있을때만 내가 작용할거야" 라는 의미를 부여하고 싶을때 사용합니다.

예를 들어, 어떤 개체(instance)의 Wrapper를 만든다면…
그 Wrapper놈은 주입되는 인스턴스 개체(source)의 생명주기에는 관여하지 않는 것이 좋을 수 있습니다.

즉,
"난 널 감싸서 사용 할거지만! 인스턴스 개체야! 너의 생명에는 관심이 없어! 네가 없어지면 나도 없어질거야! 날 위해 살아있지마! 가비지컬렉터야! 나 신경쓰지말고 이 녀석이 죽어야할때, 이 녀석을 데려가! 날 신경쓰지마!" 라고 생각해 볼 수 있습니다.

WeakReference를 사용하지 않으면 단순한 위 Wrapper 때문에 가비지에 수집되지 못합니다. (참조하고 있으니까)
위와 같은 시나리오에서는 Wrapper 따위가 인스턴스 개체의 생명주기에 관여하게 되는 것입니다.

좋아요 5