-
메모리관리(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 = nameself.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 : 1makeClone(sodeul)// ③ Instance RC : 1sodeul이 생성되는 순간 인스턴스의 RC + 1
makeClone 함수가 실행 되어 sodeul을 참조하는 clone 변수가 생성되는 순간 인스턴스의 RC + 1
함수가 종료되어 지역변수 clone이 스택에서 해제되는 순간 인스턴스의 RC -1
메모리로 예로 보면
[1] makeClone 함수가 실행된 시점
[2] makeClone 함수가 종료되어 메모리에서 해제된 시점
RC가 1인 이유는, sodeul이란 변수에 대한 참조값 1이 남은 것
죽, 인스턴스를 참조하고 있던 변수가 메모리에서 해제되면 해당 인스턴스의 RC 값은 -1이 됨
② nil이 지정되었을 때
var sodeul: Human? = .init(name: "Sodeul", age: 26) // ① Instance RC : 1var clone = sodeul // ② Instance RC : 2clone = nil // ③ Instance RC : 1sodeul = nil // ④ Instance RC : 0 (메모리 해제)만약 위 코드를 다 실행했다면 sodeul의 RC 값이 0이 되었으므로
해당 인스턴스는 ARC에 의해 메모리에서 해제될 것
③ 변수에 다른 값을 대입한 경우
var sodeul: Human? = .init(name: "Sodeul", age: 26) // ① Sodeul Instance RC : 1var clone: Human? = .init(name: "Sodeul2", age: 26) // ② Clone Instance RC : 1sodeul = 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 = emailself.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 = nameself.age = age}deinit { print("Human Deinit)" } }}let sodeul: Human? = .init(name: "Sodeul", age: 26)sodeul = nilHuman이라는 클래스 안에 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 됨
'컴퓨터상식 > 운영체제(OS)' 카테고리의 다른 글
동기/비동기 (0) 2022.03.31 모바일에서의 메인쓰레드, 프로세스 (0) 2022.03.31 메모리관리(2/3) - strong, weak (0) 2022.03.04 메모리구조 (0) 2022.03.04 [쓰레드의 모든것] 프로세스, 쓰레드, 단일/멀티 쓰레드, 동기/비동기 프로그래밍, 코어 간략 정리 (0) 2022.01.13