ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 메모리관리(1/3) - ARC
    컴퓨터상식/운영체제(OS) 2022. 3. 4. 03:21

    1. 참조(Reference) 타입과 Heap

    - ARC가 메모리 영역 중 힙 영역을 관리

    - 인스턴스, 클로저 등등 참조 타입(Reference Type)은 자동으로 힙에 할당

     

     

    실제 참조 타입을 선언할 경우 메모리가 어떤 식으로 힙에 할당되는지 예제롤 통해 파악

    class Human {
        var name: String?
        var age: Int?
        
        init(name: String?, age: Int?) {
            self.name = name
            self.age = age
        }
    }
     
    let sodeul = Human(name: "Sodeul", age: 26)

    Human이란 클래스가 있고 sodeul이라는 인스턴스를 생성하고 값을 초기화

    (예제에선 sodeul이 전역 변수가 됐지만, 어느 클래스에서 생성된 지역 변수라고 생각)

     

    메모리에서는 

     

    지역 변수 sodeul 스택에 할당 되고  실제 Human 인스턴스 에 할당 됨

     

    스택에 있는 sodeul은 힙 영역에 있는 인스턴스를 참조하고 있는 형태

    --> 따라서 sodeul 안엔 힙에 할당된 인스턴스의 주소값이 들어가 있음

     

     

    참조기 때문에 다음과 같은 작업을 해주면

    let clone = sodeul

    인스턴스가 복사되지는 않고 실제 메모리는 

     

    이렇게 sodeul과 clone이 같은 힙 영역의 인스턴스를 가리킨다

     


     

    힙의 특징 중 하나는

     

    메모리를 해제하기 위해서는 release, free 등의 방법이 있지만, 

    우린 지금껏 인스턴스를 마음대로 할당하고 사용해 왔지만  한번도 저런 함수들을 통해  인스턴스를 직접 메모리에서 해제해 준 적은 X

     

    그럼 스택에 있는 sodeul, clone이 함수 종료 시점에 사라지고 나면

     

     

     

    힙에 남은 쓰이지 않는 인스턴스는 ARC가  메모리 해제 해준다

     

     


     

    2. ARC란 무엇일까?

    ARC는 클래스 인스턴스가 더 이상 필요하지 않을 때 메모리를 자동으로 해제한다

     

    우린 지금껏 힙에 메모리를 자동 할당하며 사용해 왔지만 손수 메모리를 해제해주지 않아도 됐던 이유는  ARC라는 놈이 메모리를 자동으로 해제해주기 때문

     

    즉 ARC란, 힙에 할당된 인스턴스의 메모리를 알아서 관리해주는 기능

     

     

    2-1. GC vs RC

    힙 영역의 메모리를 관리하는 방법은 크게 GC와 RC  (ARC는 RC에 포함됨)

    가장 유명한 자바의 GC(Garbage Collection)과 비교해보면, 자바의 GC와 스위프트의 RC의 가장 큰 차이점은

    참조를 계산하는 시점

      GC RC
    참조 계산 시점 Run Time

    ▪ 어플 실행 동안 주기적으로 참조를 추적하여
    사용하지 않는 instance를 해제함

    Compile Time

    ▪ 컴파일 시점에 언제 참조되고 해제되는지
    결정되어 런타임 때 그대로 실행됨

    장점 ▪ 인스턴스가 해제될 확률이 높음
    (RC에 비해)
    ▪ 개발자가 참조 해제 시점을 파악할 수 있음
    ▪ RunTime 시점에 추가 리소스가 발생하지 않음
    단점 ▪ 개발자가 참조 해제 시점을 파악할 수 없음
    ▪ RunTime 시점에 계속 추적하는 추가 리소스가
    필요하여 성능저하 발생될 수 있음
    ▪ 순환 참조가 발생 시 영구적으로
    메모리가 해제되지 않을 수 있음

     

     

    2-2. MRC(MRR) vs ARC

    Manual vs Automatic의 차이

    MRC는 힙에 메모리를 직접 할당/해제를 해주는 것

     

    2011년 이전엔 Objective-C가 이 방법을 사용

    -> 2011년 이후부터 옵젝씨도 ARC를 사용하기 시작함

    (2014년에 나온 Swift는 당연히 ARC)

     

    그래서 오래된 코드 보면 가끔

    이런 retain, release 같은 함수들을 볼 수 있는데, 이것들이 바로 MRC를 사용할 때 쓰는 메서드들

    자세히 알고싶다면 소들이의 MRC 부수기

     

    .

    .

    . 

     

    ARC는 메모리를 어떻게 관리하길래, 알아서 인스턴스를 해제할까?

    얘가 정말 더이상 필요하지 않는 인스턴스인지, 아니면 어디 구석에선가 몰래 참조되고 있는 인스턴스인지

    어떻게 구별해서 메모리 해제 할까?

     

    3. ARC의 메모리 관리 방법

    Reference Count 를 이용

     

    메모리의 참조 횟수(RC)를 계산하여, 참조 횟수가 0이 되면 더 이상 사용하지 않는 메모리라 생각하여 해제함

     

    다시 말해 RC이 인스턴스를 현재 누가 가리키고 있느냐 없느냐(참조하냐 안하냐)를 숫자로 나타낸 것

     

    만약 참조 횟수가 10이라면 해당 인스턴스가 10군데에서 참조되고 있단 뜻이고,

    참조 횟수가 0이라면 아무데서도 참조되지 않으니 필요없다! 메모리 해제해라! 란 뜻

     

    따라서!!! ⭐️ 모~~~~든 인스턴스는 자신의 RC 값을 가지고 있다⭐️ (인스턴스가 생성될 때 힙에 같이 저장됨)

     

     

     

    그럼 이 RC는 어떤 기준으로, 어떻게 셀까?

     


     

    3-1. 참조 횟수 Count Up (+)

    참조 횟수가 +1이 되는 순간은 인스턴스의 주소값을 변수에 할당할 때

     

     ① 인스턴스를 새로 생성할 때 

    let sodeul = Human(name: "Sodeul", age: 26)

     

    아까 살펴봤던 이 코드는 실행되는 시점에

    지역 변수 sodeul은 스택에 할당되고 실제 인스턴스는 힙에 할당

    --> 그리고 sodeul엔 힙에 할당된 인스턴스의 주소값이 들어간다

     

    이렇게 인스턴스를 새로 생성할 때 (새로운 변수에 대입할 때) 해당 인스턴스에 대한 RC가 증가

     

     ② 기존 인스턴스를 다른 변수에 대입할 때 

    let clone = sodeul

    기존 인스턴스를 다른 변수에 대입할 때도 당연히 참조에 의하기 때문에  RC 값이 증가

     

    clone이란 변수에 0x1111111이란 인스턴스의 주소값이 대입 되었으니!!

     

     

     

    3-2. 참조 횟수 Count Down (-)

    참조횟수가 감소하는 경우는 크게 4가지 경우 존재

     

     ① 인스턴스를 가리키던 변수가 메모리에서 해제되었을 때 

    func makeClone(_ origin: Human) {
        let clone = origin                          // ② Instance RC : 2
    }
     
    let sodeul = Human(name: "Sodeul", age: 26)     // ① Instance RC : 1
    makeClone(sodeul)
                                                    // ③ Instance RC : 1

    sodeul이 생성되는 순간 인스턴스의 RC + 1

    makeClone 함수가 실행 되어 sodeul을 참조하는 clone 변수가 생성되는 순간 인스턴스의 RC + 1

    함수가 종료되어 지역변수 clone이 스택에서 해제되는 순간 인스턴스의 RC -1

     

    메모리로 예로 보면

    [1] makeClone 함수가 실행된 시점

     

    [2] makeClone 함수가 종료되어 메모리에서 해제된 시점

     

     

    RC가 1인 이유는,  sodeul이란 변수에 대한 참조값 1이 남은 것

     

    죽, 인스턴스를 참조하고 있던 변수가 메모리에서 해제되면 해당 인스턴스의 RC 값은 -1이 됨

     

     

     ② nil이 지정되었을 때  

    var sodeulHuman= .init(name: "Sodeul", age: 26)      // ① Instance RC : 1
    var clone = sodeul                                       // ② Instance RC : 2
     
    clone = nil                                              // ③ Instance RC : 1
    sodeul = nil                                             // ④ Instance RC : 0 (메모리 해제)

    만약 위 코드를 다 실행했다면 sodeul의 RC 값이 0이 되었으므로 

    해당 인스턴스는 ARC에 의해 메모리에서 해제될 것

     

     

     ③ 변수에 다른 값을 대입한 경우 

    var sodeulHuman= .init(name: "Sodeul", age: 26)    // ① Sodeul Instance RC : 1
    var cloneHuman= .init(name: "Sodeul2", age: 26)    // ② Clone Instance RC  : 1
     
    sodeul = clone                                         // ③ Clone Instance RC  : 2, Sodeul Instance RC : 0 (메모리 해제)

    sodeul에 clone의 값을 대입하게 되면

    sodeul의 RC는 -1

    clone의 RC는 +1

     

    당연히 sodeul 변수에 저장된 주소값이 바꼈으니 참조 카운터도 변하는 것

     

    따라서 sodeul이 가리키던 인스턴스는 RC가 0이 되었으므로 ARC에 의해 자동으로 메모리에서 해제

     

     

     ④ 프로퍼티의 경우, 속해 있는 클래스 인스턴스가 메모리에서 해제될 때 

    class Contacts {
        var email: String?
        var address: String?
        
        init(email: String?, address: String?) {
            self.email = email
            self.address = address
        }
        deinit { print("Contacts Deinit)" } }
    }

    class Human {
        var name: String?
        var age: Int?
        var contacts: Contacts= .init(email: "o_o@naver", address: "Suwon")
        
        init(name: String?, age: Int?) {
            self.name = name
            self.age = age
        }
        deinit { print("Human Deinit)" } }
    }
     
    let sodeul: Human= .init(name: "Sodeul", age: 26)
    sodeul = nil

    Human이라는 클래스 안에 contacts라는 클래스 인스턴스가 프로퍼티로 존재

    따라서 Human 인스턴스 sodeul을 생성하면 sodeul은 물론, 프로퍼티인 contacts 인스턴스도 생성되며 두 인스턴스 각각의 RC가 증가됨

     

    메모리로 보면

     

    하지만 contacts는 sodeul이 가리키던 인스턴스에 소속된 프로퍼티이기 때문에,

    sodeul이 가리키던 인스턴스가 메모리에서 해제될 경우, contacts의 RC가 하나 감소

     

    단계적으로 설명하면

    [1] sodeul에 nil을 할당한 순간, sodeul이 가리키고 있던 Human Instance의 RC가 1 감소한다

     

     

    [2] Human Instance의 RC가 0이 되었으니 메모리에서 해제된다

    이때, Human Instance가 품고 있던 contacts란 프로퍼티도 같이 메모리에서 해제되니, 

    contacts란 프로퍼티가 가리키고 있던 Contacts Instance의 RC가 1 감소한다

     

     

     

    [3] Contacts Instance의 RC도 0이 되어 메모리에서 해제된다

    유의할 점은

    sodeul이 가리키던 Human 인스턴스가 메모리에서 해제된다 해서,

    프로퍼티인 contacts가 가리키던 Contacts 인스턴스의 메모리도 같이 해제되는 것이 아님!!

    Contacts 인스턴스의 RC가 -1 감소할 뿐!

     

    만약 RC가 감소했는데 위 예제처럼 0이 되면, 그땐 Contacts 인스턴스도 메모리에서 해제되는 것

     

    실제 위 예제의 실행결과를 찍어보면

     

     

    sodeul이 가리키던 Human 인스턴스가 먼저 RC 0이 되어 deinit 되고,

    Human 인스턴스의 프로퍼티 contacts가 가리키던 Contacts 인스턴스도 RC가 0이 되어 deinit 됨

     

     

     

     

     

     

     

    출처:  https://babbab2.tistory.com/26?category=831129 

    댓글

Designed by Tistory.