본문 바로가기
iOS/설명

[iOS] OOM (Out of Memory) crash case - 1

by Sky Titan 2022. 11. 26.
728x90
 

The case of iOS OOM Crashes at Compass

Context

medium.com

 위의 The case of iOS OOM Crashes at Compass 포스팅을 번역한 글입니다. 

 

메모리 관리란 무엇인가?


 메모리는 우리의 디바이스에 저장된 모든 정보들에 들어있는 모든 메카니즘을 참조한다. 아이폰은 2가지의 데이터를 저장하는 방식을 가지고 있다.

 첫 번째는 아이폰의 전원이 꺼져도 데이터를 유지하는 hard drive나 디스크에 저장하는 방식이고, 두 번째는 디바이스가 꺼지면 데이터가 사라지는 RAM이라고 하는 저장장치에 저장하는 방법이다.

 

 디바이스에서 앱이 실행될 때, 시스템은 메모리를 할당할 heap이라는 RAM의 영역을 요청하게 된다. 이곳이 바로 reference type 변수들이 app이 동작하는 동안 생성되어 있는 곳이다.

 메모리 관리는 object들의 life cycle과 더 이상 object들이 필요없을 때 메모리를 해제하여 재사용할 수 있게 함으로써, heap 메모리를 관리하는 과정을 의미한다.

 

Note: value 타입들은 일반적으로 heap에 할당되지 않고 stack 영역에 할당된다. 다만, value type안에 reference type이 존재하는 경우는 예외로 heap에 할당되고 이 경우도 memory leak을 만들어 낼 수 있게 된다.

 

Out-of-memory crash


 OOM 크래시는 OS가 다른 process들에 메모리를 사용해야할 때, RAM을 초과사용하고 있는 app을 system이 죽이는 경우 발생한다.

이 경우는 app이 foreground에 있든, background에 있든 모두 발생할 수 있다.

 

 OOM 크래시를 방지하는 것은 단순히 우리 앱에만 좋은 것이 아니라, background에 있을 때도 안전하고 좋은 앱이 되는 것과도 연관이 있다. 만약 background 앱 때문에 디바이스가 느려지가나 배터리가 빨리 방전된다면 user는 해당 앱들을 종료시킬 것이기 때문이다.

 

내 앱이 사용할 수 있는 RAM의 양 


 애플은 절되로 앱에 할당할 수 있는 구체적인 RAM budget의 양을 공개하지는 않지만, 해당 StackOverflow 포스트에서는 앱이 종료되지 않기 위한 안전한 메모리 사용량에 대한 경계값을 보여주고 있다.

 

 이 포스팅에선 crash를 유발하기 위한 가능한 많은 양의 메모리를 사용하는 utility앱을 사용하여 사용한 메모리 총량을 기록하였다.

 아래 차트에 따르면 보통 디바이스 총 RAM양의 50% 정도가 앱이 크래시가 나지않고 사용할 수 있는 메모리 양의 한계인 걸로 보인다.

https://medium.com/compass-true-north/the-case-of-ios-oom-crashes-at-compass-96ec812397e8

 

OOM 크래시의 원인


OOM 크래시를 발생시키는 원인은 여러가지가 있다.

 

Retain cycle

 OOM 크래시의 거의 주된 원인으로 손꼽히는 것이다.

 

Caching

 캐싱은 중요한 메모리 혹은 계산 시간을 요구하며 자주 사용되는 object들을 다루는 개발자들에겐 굉장히 중요한 자산이다.

퍼포먼스 측면에선 굉장한 이점을 제공해줄 지라도, 캐싱은 매우 많은 양의 메모리를 사용하게 된다. 너무 많은 object들을 캐시하게 되면 다른 application들을 위한 RAM이 남아있지 않게 될 수도 있고 system이 강제로 종료시킬 수 있게 된다.

 

 대표적으로 image들이 메모리 오버로드와 관련된 주요한 덩치가 큰 캐시 항목이다.

 

Images

 이미지 렌더링은 메모리 관점에서 굉장히 expensive한 프로세스이다. 이 프로세스는 Decoding, Rendering이라는 2가지 과정으로 나뉘게 된다.

 

 Decoding단계는 display 하드웨어에 의해 해석될 수 있는 정보로 image 데이터를 변환하는 과정이다. 이 과정은 각 픽셀의 color와 투명도를 포함한다.

 

 Rendering단계는 하드웨어가 image buffer를 이용해서 실제로 스크린에 그리는 과정이다.

 

 예를 들어, UICollectionView안에 350px의 사각형 사진들을 보여주고 싶다고 가정해보자. 해당 사진들은 iPhone X (12MP 해상도)에서 찍혔다. 각 사진들의 data표현들은 image buffer에 있는 메모리 안에 load되고, 필요한 메모리의 양은 image의 width, height와 pixel별 필요한 bytes를 곱한 값으로 계산할 수 있다. (width * height * pixel별 bytes)

 

 3024 x 4032 pixel의 사진은 pixel별 16 bits를 필요로 하고 그러므로 UIImage(named:) 를 통해서 생성한 image는 3024 x 4032 x 16 bits 만큼의 메모리를 사용하게 된다. 이걸 계산한 값은 대략 93.02MB가 된다. 이건 UICollectionView에 보여질 하나의 image만을 위한 메모리 양이다.

 

 메모리를 초과사용하는 대표적인 케이스가 iPad에서 발생한다. 왜냐하면 대화면 스크린에다가 높은 해상도를 가지고 있으니 더 높은 퀄리티의 이미지를 사용해야하는데, iPad들의 성능은 보통 iPhone보다 좋지 않다. 이런 많은 이미지 사용은 system이 앱을 종료시킬 수 있게 된다.

 

Crash의 가시성을 높이기


 크래시를 발견하기 위해 가시성을 높일 수 있는 방법엔 뭐가 있을까?

 

Crash Reporting tool

 Google Firebase에 Crashlytics라는 크래시 리포팅 툴이 있다. 이전에 Twitter에 소유된 Fabric이라는 플랫폼의 일부였다.

 

 패브릭은 2016년 8월 OOM 크래시를 감지하는 기능을 출시했지만 인수 후 Crashlytics로 마이그레이션되지 않아 OOM 블랙박스를 열지 못했다. 이 포스팅 작성 당시 구글 팀은 이 기능을 크래시리틱스에 다시 추가할 의도가 없는 걸로 확인된다.

 

OOM 크래시를 트랙킹

 Fabric과 해당 플랫폼의 OOM 크래시 탐지기능을 사용할 수 없기에 대체품을 찾아야 한다. 그리고 우리는 향후에 Fabric에 의해 채택되는, Facebook에 의해서 발간된 기술에 대해서 읽어 보았다.

 

 이 알고리즘은 마지막 세션이 종료된 상황에 기반하여 app이 시작된 이유를 구분하는 것으로 구성되어있다.

이는 추론 및 제거 단계로 구성되며, 앱이 종료된 아무런 이유가 식별되지 않으면 OOM 크래시라는 한 가지 가능한 원인만 남기게 된다.

 

 해당 알고리즘은 OOM을 Foreground (FOOM), Background (BOOM) 케이스로 나누어 구분한다.

 이 체계적인 접근 방식은 좋아보이지만, 다소 신뢰할 수 없고 잘못된 판단이 발생하기 쉽다는 평판을 받았으며, 이것이 구글이 해당 기능을 Crashlytics에 포함하지 않기로 결정한 이유일 것 같다.

 

low memory warnings 트랙킹

 위에 소개된 방식 대신, app이 시스템에 의해 종료될 때 트리거링되는 Memory Warning을 트랙킹할 수 있다.

AppDelegate에서 applicationDidReceiveMemoryWarning 콜백 메서드에서 memory 사용량을 계산하고 추적할 수 있는 로직을 넣는 방식이다.

 

장점:

  • 매일 매일, 얼마나 많은 user들이 memory pressure를 받고 있는지 알 수 있다.
  • App health와 관련된 대시보드를 만들어서 발생할 수 있는 spike들을 시간별로 파악할 수 있다.

 

단점:

  • Low memory warning이 항상 crash로 이어지진 않는다.
  • Low memory warning은 OOM 크래시 전에 항상 발생하진 않는다.
  • 다른 앱에 의한 Memory stress또한 해당 warning을 트리거링 시킨다.

 

기억해야할 것은 Low Memory warning은 정확한 것이 아니다. 하지만 시간이 지남에 따라 발생할 수 있는 Curve, Spike 등을 보고 판단하여 새로운 OOM 크래시를 구분할 수 있게 해준다.

 

좀 더 나은 생각:

  • warning이 발생할 때 UIViewController의 subtree 구조를 log하여 어느 곳에서 메모리 압박을 더 많이 받는지를 확인할 수 있다.
  • UIViewController의 didReceiveLowMemoryWarning이라는 메서드를 통해서 해당 event를 탐지할 수 있다. Warning이 발생할 때 살아있던 Controller들은 전부 해당 메서드를 실행시킬 것이다.

 

Tracking Retain Cycles

 앱의 Retain cycle의 숫자를 추적하는 것 또한 또 다른 메트릭이 될 수 있다. Memory leak과 OOM의 주된 원인이 Retain cycle이라는 것을 우리는 알고 있다. 그렇기에 Retain cycle의 숫자와 OOM 크래시의 숫자는 상관관계가 있다. 이걸 추적하고 고치는 것은 기술적인 관점에서 가장 완벽하게 OOM crash를 막을 수 있는 방법이다.

 

 Retain cycle이 탐지될 때마다 app이 Analytics event를 보낸다. 이걸로 차트를 그리고 해당 차트를 통해 memory 상태를 탐지하는데 도움을 준다.

 

장점:

  • app crash 전에 발생하는 모든 retain cycle을 수집할 수 있다.

단점:

  • OOM crash에 대한 하나의 원인에만 추적하게 된다.

 

728x90

댓글