메모리 관리 이해

동그라미·2024년 12월 13일
5
post-thumbnail

메모리와 디스크 기본 개념


메모리와 디스크는 모두 컴퓨터나 스마트폰에서 데이터의 저장 및 처리를 담당하지만 목적과 특성이 다르다.

▪️ 메모리

  • 일반적으로 RAM 을 말하는 경우가 많다.
  • 맥북에서도 몇 GB 짜리 RAM 을 사용하는지 볼 수 있다.
  • RAM 은 휘발성 메모리이다. 즉, 데이터를 영구적으로 저장하지 않는다. 일시적인 저장에 사용한다. → 앱 실행중에 메모리에 저장된 데이터들은 앱을 종료하면 함께 삭제된다. (휘발된다) → 앱도 결국 데이터 덩어리이기 때문에, 실행을 시키면 메모리에 올라간다. → 그렇기 때문에 메모리에 저장된 데이터는 앱이 메모리에서 내려올 때 같이 내려오게 되는 것. → RAM 의 용량이 클 수록, 동시에 실행시킬 수 있는 앱의 총량이 높아진다고 생각할 수 있다.
  • 디스크보다 속도가 빠르다. (CPU 가 디스크보다 메모리에 더 빨리 접근할 수 있다.)
  • 디스크에 비해 용량이 작다. (보통 8GB, 16GB, 32GB)
  • EEPROM 과 같은 비휘발성 메모리도 있다. 아이폰은 이곳에 장치의 일련번호 및 하드웨어 정보를 저장한다.

▪️ 디스크

  • 영구적인 데이터를 저장하는 곳. 비휘발성 장치. → 앱 실행중에 디스크에 저장된 데이터들은 앱을 종료해도 디스크에 남는다.
  • 파일, 문서, 프로그램 등 상대적으로 용량이 큰 정보들을 담을 수 있다.
  • 메모리에 비해 속도가 느리다.
  • UserDefaults, CoreData 를 활용해서 디스크에 데이터를 저장할 수 있다.

💻 다음 주어진 상황들에서, 메모리를 활용하는게 좋을지 디스크를 활용하는게 좋을지 생각해봅시다. (추후 수정 예정)

1. 전화번호부 앱에서 친구의 이름, 전화번호 정보 데이터.
2. 카운터 앱 개발할 때 사용했던 number 변수.
3. 스테이지가 있는 게임 앱에서 유저가 몇 스테이지까지 클리어했는지 정보.
4. 유튜브나 인스타그램 같은 SNS 앱에서 추천 창에 뜬 이미지와 동영상 정보들.


💻 비유와 함께 개발할 때 메모리 관리가 왜 중요한지 이해해봅시다.

  • 일류 요리사가 있습니다.
  • 조리대에 수많은 식재료들이 있고, 열심히 요리를 하고 있습니다.
  • 조리대의 크기가 클수록 한꺼번에 올려놓을 수 있는 식재료의 양이 많아지겠죠.
  • 조리대의 크기는 메모리 (RAM) 의 크기에 비유해서 생각할 수 있습니다. RAM 의 크기가 클 수록 한꺼번에 올려놓고 처리할 수 있는 데이터의 양이 많아지는 것과 같습니다. 요리사는 CPU 입니다.
  • 매우 넓은 조리대가 있고, 요리사는 열심히 요리를 하느라 정신이 없어서, 더 이상 사용하지 않는 식재료를 정리하면서 동시에 요리하기가 힘듭니다.
  • 하지만 더 이상 필요하지 않은 식재료가 조리대의 자리를 차지하면 정작 필요한 재료들을 새로 올려놓을 때 좋지 않겠죠.
  • 하지만 더 이상 필요하지 않은 식재료가 조리대의 자리를 차지하면 정작 필요한 재료들을 새로 올려놓을 때 좋지 않겠죠.
  • 이때 센스있는 조교가 나타납니다. 이 조교는 조리대를 슥 훑어보고 “지금부터 양배추와 토마토는 더 이상 사용하지 않는군.” 하고 알아서 조리대에서 양배추와 토마토를 정리해줍니다.
  • 그렇게 함으로써 요리사는 조리대를 더 효율적으로 사용할 수 있게 됩니다.
  • 이 조교는 Garbage Collector (가비지 컬렉터) 입니다.

💻 Garbage Collector

Garbage Collector 는 직역하면 쓰레기 청소부. 메모리에서 필요없는 것들을 정리해주는 역할을 합니다. 사용하지 않는 데이터들이 메모리에 올라와 공간을 차지하고 있는 것은 매우 비효율적입니다. 따라서 좋은 개발자는 메모리 관리를 신경써서 잘 할 줄 알아야 합니다.

사용하지 않는 메모리가 쌓이고 쌓여서 메모리에 부담이 되는 상황을 메모리 누수 (Memory Leak) 이라고 합니다.

Java 에서는 개발자가 직접 명시적으로 메모리 관리를 하지 않더라도 기본적으로 메모리 관리를 돕는 GC 라는 시스템이 있습니다.

GC 가 동작하는 방식을 간단하게만 소개하면, 런타임에 메모리 영역을 슥 훑어보며 사용중인 것들을 표시 (Mark) 하고, 표 되지 않은 모든 것들을 정리해버리는 Mark-and-Sweep 방식을 사용합니다.

🤔 왜 갑자기 Java 의 메모리 관리 시스템을 공부하나요?

일반적인 메모리 관리 개념의 근간이 되는 시스템이며, 개발자라면 필수 교양으로 알아야 하는 내용이기 때문에 간단하게 공부해보았습니다.


Reference Counting

메모리를 할당 받은 객체를 인스턴스라고 합니다.

예를들어 아래 코드에서 myClass 는 인스턴스가 된 것이죠.

class MyClass {}

// 메모리를 할당받음. 인스턴스.
let myClass = MyClass()

인스턴스는 하나 이상의 참조자(소유자=owner) 가 있어야 메모리에 유지가 됩니다. 소유자가 없다면 즉시 메모리에서 제거가 됩니다. 이때 인스턴스를 참조하고 있는 소유자의 개수를 reference count 라고 합니다.

reference count > 0 이면 메모리에 살아있고, reference count = 0 이면 메모리에서 삭제됩니다.

그렇기 때문에, 더 이상 사용하지 않을 인스턴스의 reference coutn 가 0보다 크지 않도록 주의를 해야합니다.

class MyClass {
    init() {
        print("MyClass 생성")
    }
    deinit {
        print("MyClass 소멸")
    }
}

// RC = 1
var myClass: MyClass? = MyClass()

// RC = 2
var myClass2 = myClass

// RC = 2-1 = 1
myClass = nil

오늘의 스스로 숙제!
클래스의 deinit 소멸자 메서드는 메모리에서 해제될때 호출됩니다. 위 코드에서 deinit 의 프린트가 찍힐까? Reference Count 의 개념을 생각하며 결과에 대한 이유를 생각해보자


ARC 와 MRC

  • ARC = Automatic Reference Counting

  • MRC = Manual Reference Counting

  • ARC

    • ARC 는 Swift 의 메모리 관리 시스템. Java 에 GC 가 있다면 Swift 에는 ARC 가 있음.
    • Reference Count 를 자동으로 계산. (Automatic)
      • 객체가 생성될 때 RC 가 1 로 설정
      • 객체가 다른 변수나 속성에 할당되어 참조될때마다 RC 가 1 씩 증가
      • 객체에 대한 참조가 해제될때마다 RC 가 감소
      • RC 0 이 되면 더 이상 사용되지 않는 것으로 간주되어 메모리에서 해제.
  • MRC

    • MRC 는 Objective-C 에서 사용하는 메모리 관리 시스템.
    • Reference Count 를 개발자가 코드로 직접 계산. (Manual)
      • 객체가 생성될때 개발자가 명시적으로 메모리 할당
      • 객체를 다른 변수나 속성에 할당되어 참조될때마다 개발자가 명시적으로 RC 증가
      • 객체에 대한 참조가 해제될때마다 개발자가 명시적으로 RC 감소
      • RC 가 0 이되면 개발자가 명시적으로 메모리에서 해제.

    🙋🏻‍♂️ 그렇다면 ARC 는 자동으로 RC 카운트를 해서 메모리 관리를 해주는 좋은 시스템이니, 개발자는 메모리 관리에 대해 신경쓰지 않아도 되나요?

→ 그렇지 않습니다. ARC 로 잡아내지 못하는 메모리 누수 상황이 발생할 수 있기 때문에, 개발자는 메모리 관리 방법을 반드시 알아야 합니다.

약참조와 강참조

  • 약참조
    • Reference Count 를 증가시키지 않으면서 참조하는 것.
    • weak 키워드를 붙여서 약참조를 할 수 있다.
  • 강참조
    • Reference Count 를 증가시키면서 참조하는 것.
    • 일반적인 참조 방식을 말한다.

클로저의 캡처링 개념

Swift 의 클로저 안에서 값을 사용하는 방법 중에는 캡처링 이 있습니다.

class Adam {
    let mbti = "ENTJ"
    init() {
        print("클래스 생성")
    }
    deinit {
        print("클래스 소멸")
    }
}

// adam rc = 1
var adam: Adam? = Adam()

// 클로저 내부에서 adam 캡처. rc 1 증가. adam rc = 2
let printMbti: () -> () = { [adam] in
    guard let adam else { return }
    print("adam's mbti = \(adam.mbti)")
}

printMbti()

// adam rc = 2-1 = 1
adam = nil

printMbti 라는 클로저를 선언했고, 클로저 내부에서 클로저 외부의 adam 이라는 객체를 가져다 쓰고 싶으면 값을 캡처 해야합니다. 이때 [ ] 로 감싸면 값을 캡처링해서 클로저 내부에서 사용할 수 있게 됩니다.

🌟 클로저 내부에서 클래스의 값을 캡처하면, Reference Count 가 증가합니다.

위 예시를 따라해보면 adamdeinit 소멸자가 호출되지 않습니다. 클로저에서 값을 캡처해 rc 가 증가했기 때문입니다.

위 코드를 개선해서, 메모리 누수가 발생하지 않는 상황을 만들려면 다음과 같이 코드를 작성해야 합니다.

class Adam {
    let mbti = "ENTJ"
    init() {
        print("클래스 생성")
    }
    deinit {
        print("클래스 소멸")
    }
}

// adam rc = 1
var adam: Adam? = Adam()

// 클로저 내부에서 adam 캡처.
// weak 참조 했으므로 rc 가 증가하지 않음. adam rc = 1
let printMbti: () -> () = { [weak adam] in
    guard let adam else { return }
    print("adam's mbti = \(adam.mbti)")
}

printMbti()

// adam rc = 1-1 = 0
adam = nil

위 코드를 따라서 실행해보면, 메모리가 해제되고 deinit ”클래스 소멸” 이 호출되는 것을 확인할 수 있습니다.


순환 참조 (Circular Reference)


A 가 B 를 참조하고 (A→B),
B 가 A 를 참조해서 (B→A), 서로가 서로를 참조하는 상황을 순환 참조라고합니다.
일반적으로 순환 참조는 메모리 누수를 발생시키는 대표적인 사례입니다.
아래 예시를 보고 순환 참조 개념을 이해해봅시다.

class Person {
    var pet: Dog?
    init() {
        print("Person 클래스 생성")
    }
    deinit {
        print("Person 클래스 소멸")
    }
}

class Dog {
    var owner: Person?
    init() {
        print("Dog 클래스 생성")
    }
    deinit {
        print("Dog 클래스 소멸")
    }
}

// person rc = 1
var person: Person? = Person()
// dog rc = 1
var dog: Dog? = Dog()

// dog rc = 2
person?.pet = dog
// person rc = 2
dog?.owner = person

// person rc = 1
person = nil
// dog rc = 1
dog = nil

→ 따라서, 개발자는 개발할 때 순환참조가 발생하는 상황이 아닌지 점검할 줄 알아야 합니다.

profile
맨날 최선을 다하지는 마러라. 피곤해서 못산다.

6개의 댓글

comment-user-thumbnail
2024년 12월 16일

살아있나요?

1개의 답글
comment-user-thumbnail
2024년 12월 17일

오늘은 화요일이니까 조심스럽게 이 말 해도될까요..?
저랑 코드리뷰하실래요?

답글 달기
comment-user-thumbnail
6일 전

의외로 수요일에 해도 되는 말
네 코드랑 내 코드 공유 할래?

1개의 답글
comment-user-thumbnail
3일 전

제 램 늘려주실 세모네모님

답글 달기