[Kotlin] Weak Reference

이현빈
9 min readFeb 28, 2023

--

https://unsplash.com/ko/%EC%82%AC%EC%A7%84/cckf4TsHAuw

weak Reference 란 무엇일까요?

Reference의 종류는

  • Strong Reference
  • Soft Reference
  • Weak Reference
  • Phantom Reference

4가지로 분류할 수 있습니다.

Reference는 적절한 상태에 따라 GC가 제거할 데이터에
우선 순위를 부여하여 호율적으로 메모리를 관리하기 위해 분류됩니다.

참조되는 타입에 따라 GC의 대상이 되는 경우와
실행되는 시점이 결정됩니다.

여기서 GC란

GC란 Garbage Collection을 뜻하는 것으로 쓰레기를 수집한다는 의미를 담고 있습니다.

메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다.

라고 위키백과에서 설명하고 있습니다.

GC는 힙(heap) 내의 객체 중에서 가비지를 찾아내고,
찾아낸 가비지를 처리하여, 힙의 메모리를 회수하는 역할을 합니다.

더욱 자세한 내용은 따로 정리하겠습니다.

Referenece는
Strong < Soft < Weak < Phantom 순으로 GC에 의해 우선적으로 제거됩니다.

하나씩 무엇을 의미하는 것인지 알아보겠습니다.

Strong Reference

흔히 객체를 생성하게되면 생기는 참조 형태입니다.

Strong Reference를 통해 참조되고 있는 객체는
가비지 컬랙션의 대상에서 제외가 됩니다.

fun main() {
var printer: Printer? = Printer()
printer?.printer()

printer = null
}

class Printer() {
fun printer() {
println("printing $this")
}
}

// 출력 결과
// printing Gfg@279f2327

해당 로직은 Strong Reference로 Gfg 클래스의
인스턴스를 생성하여 g 변수에 할당합니다.

printer의 메서드 printer()을 호출합니다.

그리고 printer에 null을 할당하여, Heap 메모리의
Printer 클래스의 인스턴스를 unreachable 상태로 변경하여,
향후 GC에 의해 제거될 수 있습니다.

Soft Reference

해당 상태는 GC에 의해 수거될 수도 있고, 수거되지 않을 수도 있는 상태입니다.

메모리에 여유가 있다면 GC에 의해 수거되고,
메모리가 충분하다면 GC에 의해서 수거되지 않는 상태입니다.

import java.lang.ref.SoftReference

fun main() {
var printer: Printer? = Printer()
val soft: SoftReference<Printer> = SoftReference(printer)

println(soft.get())

printer = null

println(soft.get())
}

class Printer() {
fun printer() {
println("printing $this")
}
}

// Printer@279f2327
// Printer@279f2327

메모리의 용량이 적은 모바일 기기의 경우 weak reference와 비슷하게
작동할 수 있지만,

메모리가 부족한 상황(OutOfMemoryError) 전까지 수거되지 않습니다.

Strong Reference를 하는 경우에 Image를 cache에 로딩할 때
Image를 cache할 때 메모리에 있는 이미지에 대한 참조를 항상 포함해야 하는데

그 참조 자체가 이미지를 메모리에 남겨두도록 강제하기 때문에 수동으로 처리하지
않으면 OutOfMemoryError가 생길 수 있다는 문제가 있습니다.

하지만 이를 Soft Reference로 한다면
메모리가 부족해질 경우 이미지를 메모리에서 처리해주기 때문에
메모리를 관리해줄 수 있습니다.

Weak Reference

해당 상태는 Strong 또는 Soft와 같은 참조가 없다면
GC에 의해 수집되는 상태입니다.

해당 상태는 명시적으로 해당 객체가 GC에 의해 수거될 수 있도록 유도합니다.

하지만 해당 상태라고 즉각적으로 gc에 의해 처리되는 것을 보장하지는 않고,
실직적인 메모리 회수 시점은 GC 알고리즘에 따라 다를 수 있습니다.

import java.lang.ref.WeakReference

fun main() {
var printer: Printer? = Printer()
val weak: WeakReference<Printer> = WeakReference(printer)

println(weak.get())

printer = null
println(weak.get())
}

class Printer() {
fun printer() {
println("printing $this")
}
}

Strong Reference로 Printer 클래스의 인스턴스를 생성하여 printer 변수에 할당합니다.

printer 의 메서드 printer()를 호출합니다.

printer 변수로 Weak Reference인 weak를 생성합니다.

printer에 null을 할당하며 printer를 Weak reachable 상태로 만듭니다.

GC가 동작하여 메모리 회수 대상이 됩니다.

이후에 메모리 회수가 정상적으로 이루어졌다면
printer에서 printer()를 호출 하였을 때 (NPE)NullPointerException이 발생하고,

그렇지 않다면 printer() 메서드가 실행됩니다.

해당 상태는 GC에 의한 메모리 회수 우선 순위가 높기 때문에
캐싱에 활용도가 높습니다.

Soft Reference와 Weak Reference를 조금 비교해보자면

두 상태가 조금 명확하지 않다고 생각할 수 있습니다.

하지만 해당 상태는 Soft Reference는 메모리의 상태에 따라 GC의 대상이 되는 반면
Weak Reference는 대상 객체를 참조하는 경우가 Weak Reference 객체만 존재하는 경우
GC의 대상이 되기 때문에 다음 GC 실행 시 힙 메모리에서 제거됩니다.

두 특성을 비교하여 가까운 시일 내 다시 참조될 수 있는 경우엔 Soft Reference를 사용하면 좋습니다.

Phantom Reference

해당 상태는 주로 사용하는 상태는 아닙니다.

아래 코드를 보시면

Soft, Weak 상태와는 달리 get()을 사용하면 언제나 null을 리턴합니다.

보통 해당 객체가 죽었는지 살았는지 판단하기 위해 사용됩니다.

해당 상태는 사용하기 위한 상태보다는 올바르기 삭제하고, 삭제 이후 작업을 조작하기 위합니다.

해당 상태는 finalize()메서드나 enqueue와 같은 내용이 나옵니다.

finalize()란

약간 풀어서 설명하자면
해당 객체가 종료될 때 사용된다라고 생각할 수 있습니다.

finalize()를 커스텀하여,
객체의 종료 시점에 사용할 수 있습니다.

하지만 finalize()에 경우 단점이 존재합니다.

바로 어느 시점에 호출이 되는지, 메모리에서 언제 해제 된다는지 등
정확하게 알지 못해 불편함이 있었습니다.

또한 해당 로직에서 finalize()의 내부 구현에 따라 resurrection,
즉 객체가 죽었다가 다시 부활할 수 있습니다.

그리고 성능 저하가 발생할 수 있습니다.

또한 예외가 발생하면 무시되는 등의 불편한 점들이 있습니다.

따라서 Phantom Reference를 사용할 수 있습니다.

Phantom Reference는 후처리는 진행하는 과정에서 정밀한 작업을 진행하기 위해 사용됩니다.

Phantom Reference는 메모리에서 PhantomReference의 참조값이 메모리에서 제거된 후, referenceQueue에 삽입됩니다.

이후에 phantomReference의 요소들이 referenceQueue에 삽입되었는지 확인합니다.

그리고 Phantom Reference가 제거된 후 처리해야할 로직을 수행합니다.

이후 Phantom Reference에서 clear() 메소드를 직접 수행합니다.

Phantom Reference를 사용한다면 finalize() 메소드에 비해서 정교한 후처리가 가능하고,
Phantom Reference의 경우 해당 객체가 물리적으로 제거된 이후에 enqueue되기 때문에 finalize() 때문에 객체가 다시 살아날 수 없습니다.

마무리

언어의 조금 낮은 부분에 들어가다보니 부족하게 설명하고 넘어간 부분도 있고, 충분히 테스트하며 코드로 작성하여 사례를 들지 못했다는 부분에서 조금 아쉽긴하지만, 평소에 자주 접하지 못한 내용을 알게 되다보니 그래도 재미있는 공부였습니다.

--

--