이미지 출처: Naver D2
동작 방식
class A {
int i = 5;
}
class B {
A a = new A();
}
class C {
B b;
public static void main(String args[]) {
C c = new C();
c.b = new B();
// instance of A, B, and C created
c.b = null;
// instance of B and A eligible to be garbage collected.
}
장단점
: 객체의 레퍼런스 카운팅(reference counting, 참조 수)을 제공하는 메모리 관리 기법입니다.
이미지 출처: Apple Documentation Archive
레퍼런스 카운트가 오르는 경우
레퍼런스 카운트는 클래스 인스턴스(Class Instance) 등의 레퍼런스 타입 값을 변수에 할당할 때 증가합니다. 클래스 인스턴스를 변수에 할당할 때 메모리에서는 힙(heap) 영역에 저장된 instance meta data의 주소값을 변수에 할당하는 작업이 일어납니다. 즉, 메모리의 힙(heap) 영역에 저장되어 있는 instance meta data의 주소값이 변수에 할당되는 시점이 참조 카운트가 증가하는 시점입니다.
이미지 출처: https://velog.io/@cskim
레퍼런스 카운트가 내려가는 경우
레퍼런스 카운트가 감소하는 시점은 인스턴스를 참조하던 stack 영역의 변수들이 메모리에서 해제될 때 입니다.
이미지 출처: https://velog.io/@cskim
메모리 누수는 언제 일어날까?
서로 다른 인스턴스가 서로를 강하게 참조하고 있어서 참조 횟수를 0으로 만들지 못하고 영원히 메모리에서 해제되지 않는 순환 참조 관계일 때 메모리 누수가 발생합니다.
강한 참조(Strong Reference)와 강한 참조 순환(Reference Cycle) 예시
class Swift {
var swiftReference: Java?
}
class Java {
var javaReference: Swift?
}
var swift: Swift? = Swift() // swift Reference Count: 1
var java: Java? = Java() // java Reference Count: 1
swift?.swiftReference = java // java Reference Count: 2
java?.javaReference = swift // swift Reference Count: 2
swift = nil // swift Reference Count: 1
java = nil // java Reference Count: 1
해결 방법
강한 참조 순환 문제를 해결하기 위한 방법으로는 약한 참조(weak reference)와 미소유 참조(unowned reference) 참조 두 가지가 있습니다. 두 참조는 순환 참조 상황에 있는 A 인스턴스가 다른 B 인스턴스를 강하게 잡고 있지 않도록(인스턴스를 참조하지만 RC를 증가시키지 않도록) 하여 강한 참조 순환을 만들지 않도록 합니다.
Weak Reference(약한 참조)
약한 참조는 해당 참조가 남아있더라도 ARC는 인스턴스를 메모리에서 해제시킬 수 있습니다. 앞서 언급한 대로 A 인스턴스가 다른 B 인스턴스를 강하게 잡고 있지 않기 때문입니다. ARC는 자동으로 약한 참조를 하는 객체에 nil을 할당할 수 있습니다. 이때, 런타임에 객체에 nil을 할당해야 하기 때문에 옵셔널 타입의 변수(var)로 선언해야 합니다.
약한 참조는 강한 참조 순환 문제를 만드는 두 인스턴스에서 다른 한쪽의 인스턴스가 상대적으로 먼저 메모리에서 해제될 가능성이 있을 때 사용하면 됩니다. ( Ex - delegate )
class Swift {
var swiftReference: Java?
}
class Java {
weak var javaReference: Swift? // 옵셔널 타입의 변수
}
var swift: Swift? = Swift() // swift Reference Count: 1
var java: Java? = Java() // java Reference Count: 1
swift?.swiftReference = java // java Reference Count: 2
java?.javaReference = swift // swift Reference Count: 1
swift = nil // swift Reference Count: 0 -> java Reference Count: 1
java = nil // java Reference Count: 0
Unowned Reference(미소유 참조)
미소유 참조도 약한 참조와 마찬가지로 레퍼런스 카운트를 증가시키지 않으면서 인스턴스를 참조합니다. 하지만 미소유 참조는 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 가정하기 때문에 약한 참조와 달리 암묵적으로 옵셔널을 해제(!
)하여 선언합니다.
미소유 참조는 참조하고 있던 인스턴스가 먼저 메모리에서 해제될 때 nil
을 할당할 수 없어 오류를 발생시키므로 사용시 주의해야합니다.
class SomeClass {
weak var weakVariable: Int?
unowned var unownedVariable: Int!
}
Strong 참조, Weak 참조, Unowned 참조 비교표
strong | weak | unowned | |
---|---|---|---|
Reference Counting | O | X | X |
Variable(var ) |
O | O | O |
Constant(let ) |
O | X | O |
Optional | O | O | X |
Non-Optional | O | X | O |
Memory Release | 명시적으로 nil 할당 |
auto deinit nil 할당 |
auto deinit 메모리 주소를 계속 갖고 있음 |
Expected Problem | Strong Reference Cycle Memory Leak | 인스턴스 해제 후 접근하면 nil 반환 |
인스턴스 해제 후 접근하면 오류 Dangling Pointer |