November 15, 2020

1158 words 6 mins read

Swift의 Automatic Reference Counting(ARC) vs Java의 Garbage Collection(GC)

Swift의 Automatic Reference Counting(ARC) vs Java의 Garbage Collection(GC)

1️⃣Java의 Garbage Collection(GC)와 2️⃣Swift의 Automatic Reference Counting(ARC) 3️⃣MRR(Manual Retain-Release) or MRC(Manual Reference Counting)에 대해 소개합니다 :)


2020년 10월인가 11월부터 이펙티브 자바라는 책으로 스터디를 하고 있어요!

👉🏻👉🏻 Effective Swift

Swift라는 언어가 다른 프로그래밍 언어에 비해서 업데이트가 빨라 바뀌는게 많아서 그런지 Swift는 Effective 시리즈가 없더라구요! 그래서 Effective 시리즈 중 많은 프로그래머들에게 인정받고 있는 Effective Java 책 기반으로 Effective Swift를 해보면 어떨까? 라는 생각에 친구들과 함께 공부하고 있습니다.

내용이 쉽지 않지만 그래도 재밌어요! 🤓

아래는 제가 작성한 스터디 문서를 토대로 작성했습니다!

Item 7. 다 쓴 객체 참조를 해제하라

< 📑 목차 >

  1. Java의 Garbage Collection(GC)
    1.1. 동작방식
    1.2. 장단점

  2. MRR(Manual Retain-Release) or MRC(Manual Reference Counting)

  3. Swift의 Automatic Reference Counting(ARC)
    3.1. 동작방식
    3.2. 레퍼런스 카운팅 시점
    3.3. 장단점

  4. 메모리 누수 (Memory Leak)
    4.1. 메모리 누수는 언제 일어날까?
    4.2. 강한 참조(Strong Reference)와 강한 참조 순환(Reference Cycle) 예시
    4.3. 해결 방법
    4.4. Weak 참조(약한 참조)와 Unowned 참조(미소유 참조)
    4.5. Strong 참조, Weak 참조, Unowned 참조 비교표

  5. 참고



Java의 Garbage Collection(GC)

img

이미지 출처: Naver D2

1. 동작 방식

  • Java의 경우 JVM에 의해 힙(heap) 영역에 생성 객체가 할당됩니다.
  • JVM은 객체들이 더이상 코드에 참조되지 않을 때를 추적합니다.
  • GC는 Unreachable Objects들을 수거합니다.
  • Unreachable Objects: 유효한 최초의 참조(Root Set of References가 이루어지지 않는 객체)
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.
}
  • 런타임백그라운드에서 사용되지 않는 객체와 객체 그래프들(objects and object graphs)을 감지하는 방식으로 동작합니다. 이 동작은 일정 시간 경과한 뒤나 런타임 메모리가 낮아졌을 때 중간 간격(intermediate intervals)으로 발생하며 정확한 순간에 해제되지 않습니다.
  • GC는 사용자가 강제로 수행할 수 없고 언제 일어나는지도 불확실합니다.
  • GC는 객체를 메모리에서 제거하기 전에 해당 객체의 finalize( ) 메소드 호출합니다.

2. 장단점

  • Garbage Collection의 장점
    1. Retain Cycles을 포함한 전체 객체 그래프를 정리(clean up)할 수 있습니다.
  • Garbage Collection의 단점
    1. GC는 객체 릴리즈(objects release)에 대한 정확한 시간이 결정되지 않습니다(undetermined).
    2. Full GC가 발생하면 애플리케이션의 다른 스레드가 일시적으로 보류될 수 있습니다.
      • STW (Stop-The-World): Full GC가 발생하면 JVM은 애플리캐이션 실행을 멈추고 GC를 실행하는 쓰레드만 작동합니다.


Objective-C의 MRC(Manual Reference Counting)

Objective-C는 RC 방법으로 메모리를 관리해 왔습니다. 개발자가 메모리 할당 및 해제를 직접 관리해야 했기 때문에 MRC(Manual Reference Counting), 또는 할당(retain)과 해제(release)를 명시적으로 호출하기 때문에 MRR(Manual Retain-Release) 이라는 이름으로 불렸습니다.

../Art/ARC_Illustration.jpg

ARC가 나오기 전에는 개발자가 직접 메모리 관리에 신경을 써야 했습니다. 메모리 관리를 직접 한다는 것은 인스턴스를 메모리에 할당하고 필요하지 않을 때 메모리에서 해제시키는 코드를 적절하게 사용해야 한다는 의미입니다. ARC 이전에는 Retain Cycle을 추적하여 인스턴스를 retain 한 만큼 명시적으로 release 시켜 주어야 했습니다.

2011년부터 Objective-C의 메모리 관리 방식은 MRC에서 ARC로 대체되었고, 2014년 발표된 Swift도 ARC를 사용한 메모리 관리 방식을 채택했습니다. ARC는 개발자가 직접 작성해야 했던 retainrelease 코드를 complie할 때 생성하여 참조를 관리합니다.

기본적으로 retainrelease는 메모리의 heap 영역에 할당/해제하는 작업이기 때문에 ARC도 class instance 및 function, closure 같은 참조 타입에만 적용됩니다.

Swift의 Automatic Reference Counting(ARC)

객체의 레퍼런스 카운팅(reference counting, 참조 수)을 제공하는 메모리 관리 기법입니다.

../Art/ARC_Illustration.jpg

이미지 출처: Apple Documentation Archive

1. 동작 방식 * 레퍼런스 카운팅(Reference Counting)은 각 객체의 레퍼런스(= 참조)의 수를 계산하는 방식으로 동작합니다. * 런타임에 레퍼런스 카운트를 증가시키거나 감소시키는 메모리 참조나 해제 코드(retain이나 release)를 컴파일 때 컴파일러가 일정한 규칙에 의해 생성해 삽입하고(위 이미지 참고), 객체의 레퍼런스 카운트가 0에 도달했을 때 객체의 할당 해제를 표시합니다. * 개발자는 ARC가 언제 참조 카운트를 올리고 내리는지 규칙을 알고, 적절한 시점에 코드가 생성되도록 조절할 수 있습니다. * 레퍼런스 카운트가 0이 되면 그 객체는 확실히 접근할 수 없습니다(unreachable). * 런타임에 비동기적으로 객체를 제거합니다.

2. 레퍼런스 카운팅 시점

  • 레퍼런스 카운트가 오르는 경우
    레퍼런스 카운트는 클래스 인스턴스(Class Instance) 등의 레퍼런스 타입 값을 변수에 할당할 때 증가합니다.
    클래스 인스턴스를 변수에 할당할 때 메모리에서는 힙(heap) 영역에 저장된 instance meta data의 주소값을 변수에 할당하는 작업이 일어납니다.
    즉, 메모리의 힙(heap) 영역에 저장되어 있는 instance meta data의 주소값이 변수에 할당되는 시점이 참조 카운트가 증가하는 시점입니다.

img

이미지 출처: https://velog.io/@cskim

  • 레퍼런스 카운트가 내려가는 경우
    레퍼런스 카운트가 감소하는 시점은 인스턴스를 참조하던 stack 영역의 변수들이 메모리에서 해제될 때 입니다.

img

이미지 출처: https://velog.io/@cskim

3. 장단점

  • Automatic Reference Counting의 장점

    1. 객체가 사용되지 않을 때 실시간으로 결정론적 파괴(deterministic destruction)가 일어납니다.
      • 참고로, 결정론적 알고리즘(deterministic algorithm)은 ‘예측한 그대로 동작하는’ 알고리즘을 뜻하는데 이곳에서도 이와 같은 맥락으로 *예측 그대로 파괴된다*는 의미라고 해석할 수 있을 것 같습니다.
    2. 백그라운드 프로세싱이 없으므로 모바일 장치와 같은 저전력 시스템에서 더 효율적입니다.
  • Automatic Reference Counting의 단점

    1. Retain Cycles에 대처할 수 없습니다.

메모리 누수 (Memory Leak)

1. 메모리 누수는 언제 일어날까? 서로 다른 인스턴스가 서로를 강하게 참조하고 있어서 참조 횟수를 0으로 만들지 못하고 영원히 메모리에서 해제되지 않는 순환 참조 관계일 때 메모리 누수가 발생합니다.

2. 강한 참조(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

3. 해결 방법 강한 참조 순환 문제를 해결하기 위한 방법으로는 약한 참조(weak reference)미소유 참조(unowned reference) 참조 두 가지가 있습니다. 두 참조는 순환 참조 상황에 있는 A 인스턴스가 다른 B 인스턴스를 강하게 잡고 있지 않도록(인스턴스를 참조하지만 RC를 증가시키지 않도록) 하여 강한 참조 순환을 만들지 않도록 합니다.

4. Weak 참조(약한 참조)와 Unowned 참조(미소유 참조)

  • 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!
  }

5. Strong 참조, Weak 참조, Unowned 참조 비교표

참고

  1. Automatic Reference Counting - the swift programming language 5.3
  2. ARC vs. GC
  3. Garbage Collection vs Automatic Reference Counting
  4. [JAVA] Garbage Collection의 기초
  5. Java Reference와 GC
  6. ARC

이미지 출처

Strong 참조, Weak 참조, Unowned 참조 비교표 출처