[안드로이드와 운영체제를 섞다] 안드로이드의 메모리 관리

3
post-thumbnail

오늘은 안드로이드의 메모리 관리중 하나인, Low Memory Killer에 대해 다뤄보려고 합니다.

그전에 안드로이드는 메모리를 어떻게 관리하는지부터 알아보겠습니다.

1. 안드로이드는 어떻게 메모리를 관리하는가?

1.1 페이징

페이징은 가상 메모리와 물리 메모리를 고정된 영역으로 나누는것을 말합니다.
이해가 안되시죠!? 아래 그림을 봅시다.

process4가 들어오기 위해선, process3이나 1이 종료되어야 들어올 수 있습니다.
총 메모리 공간은 충분하지만, 실제로 할당할 수 없는 문제를 external fragment(외부 단편화)라고 부릅니다.

이를 해결하기 위해서 생각해볼수 있는건 뭘까요?

첫번째로 할수 있는것은 프로세스를 위로 최대한 당기는 방법이 있습니다.

이를 compaction이라고 하는데, 임시공간에 프로세스를 저장하고 다시 꺼내오는 방식을 이용해야합니다.
(왠지 옛날에 자바 처음배울때, 템프 변수를 만들어서 옮겼었던 기억이...)
하지만 하드디스크를 이용해야하기 때문에 상당히 느립니다.

그래서 차라리 텅빔공간(hole)를 최대한 없애기 위해 Paging 이란 기법이 생겨났습니다.

Paging은 프로세스를 동일한 크기를 잘라서 배치하는 방법을 말합니다.

중요한건 아까전과 달리 프로세스를 연속적으로 배치하지 않아도 Paging Mapping을 통해 실행할 수 있기 때문에 아래와 같은 그림이 가능합니다.


일정한 크기로 잘려진 단위를 가상메모리에선 Page, 물리메모리에선 Frame이라고 부릅니다.

1.2 페이지 아웃 ,페이지 폴트, 페이지 인


우리의 메모리는 현재 페이징을 사용하여 아주 효율적으로 프로세스를 관리하고 있습니다.
하지만 어머나!! 너무나 효율적으로 쓰고있는데도 불구하고, 결국 Memory가 꽉차고 말았습니다.

메모리가 꽉찼을때, 새로운 프로세스의 적재를 위해서는 사용중인 페이지나 프레임을 어딘가에 담아놓는것(페이지 아웃)이 필요합니다.

어디로 담아둘까요?
날아간 페이지들은 스왑스페이스(하드디스크 같은 보조 저장장치)라는 곳에 저장이 되는데요.
페이지 아웃된 페이지에 대한 요청시, 페이지 폴트가 발생하고 페이지 인(페이지 아웃과 정반대겠죠?)을 실행하여 다시 데이터를 가져오게 됩니다.
이를 통해 실제 메모리를 좀더 넓게 쓸수 있습니다.

하지만... 안드로이드는 Swap Space가 없습니다.

2. 왜 Swap 공간이 존재하지 않을까?


스왑에 대한 내용

안드로이드에서 사용하는 플래시 메모리고, 아래와같은 문제점을 지니고있습니다.

  • 내구성 : 읽기 쓰기 횟수가 제한되어있어, 스왑스페이스에 이용하기 알맞지 않음.
  • 에너지 : 스왑스페이스를 이용하면 추가적인 전력소모

결국 플래시 메모리를 사용하기에, 스왑스페이스로는 적절하지 않다는 얘기입니다.

그렇다면 어떻게 페이지 아웃을한다는 말일까요?

3. Z(zip)ram 을 만들어서 압축해서 넣어놓자

안드로이드의 Ram에는 ZRam이라는 파티션이 존재합니다.
페이지 아웃이 일어날때, Ram에서는 ZRam으로 보내게되고, 압축을 하게됩니다.
페이지인을 통해 복사하여 가져올때는 압축이 풀린 상태로 가져오게 됩니다.

4. 하지만 이렇게 했음에도 메모리가 부족하다.

결국 이렇게까지 했는데도 앱을 많이 키다보면 결국 앱은 ZRam도 꽉찰겁니다.
이제 메모리에 적재되어 있는 페이지를 정리해야할때가 온겁니다.

Understanding Android memory usage (Google I/O '18)
메모리에 있는 Page를 분류해보면 총 3가지가 있을것입니다.
사용되고 있는 페이지와 캐쉬된 페이지, 그리고 아무것도 저장되지 않은 페이지 입니다.

  • 사용된 페이지(Used Pages)
  • 캐쉬 페이지(cached Page)
  • 아직 사용되지 않은 페이지 (Free Page)

메모리를 정리하는 녀석은 총 두가지가 있습니다.

4.1 kswapd(케이스왑디)

Free Pages가 어느 임계점 아래로 내려갔을때, 안드로이드는 kswapd를 통해
Cashed Page들을 정리합니다.

그래서 Free Page가 임계점 이상으로 돌아올때까지 작동을 하게됩니다.

하지만 캐시 메모리를 계속 없애다보면, 백그라운드로 다시 갔다가 돌아왔더니 앱이 처음부터 재부팅 되겠죠? 속된 콜드 스타트..)
그래서 캐시 메모리가 임계점이상으로 줄어들때 Low Memory Killer가 작동하게 됩니다.

4.2 Low Memory Killer

리눅스에서 전체 메모리가 꽉차게 되면 어떤 현상이 벌어질까요?

기본적으로 리눅스 커널에서 메모리 꽉찼을때 OOM은 이렇게 처리합니다.

그런데 문제가 발생했습니다.

메모리를 가장 많이쓰는 프로세스가 지금 사용자에게 노출되고있고, 중요한 작업을 하던 도중이라면 많이 곤란해 질것 같아요.

이러한 문제를 해결하기위해 나온 Low Memory Killer는 2009에 작성된 리눅스 커널 모듈 입니다.

모든 프로세스에서는 액티미티 매니저가 부여한 oom_adj를 갖고 있습니다.

oom_adj는 어플리케이션의 상태 조합((Foreground, Background, Background Service 등등)입니다.

OOM 킬러는 현재 사용가능한 여유 메모리와 oom_adj 임계값을 기반으로 구성한 규칙을 사용합니다.

그래서 시스템을 사용자 관점에서 봤을때 안정적이게 만듭니다.

5. 근데 이런데도 왜 OOM이 뜨는거야?

안드로이드는 프로세스마다 Heap Size의 Limit가 존재합니다.
램의 크기만큼 heap 영역이 늘어나는게 아니라 제한이 걸려있기에, 초과하게되면 해당 프로세스는 OOM을 내뿜으며 죽어버리고 말죠.
최초의 안드로이드 단말기의 limit는 겨우 16MB였다고 합니다.
물론 Limit는 기종마다 다르기 때문에 현재 갤럭시 S22같은 고성능 단말기는 더 높은 메모리 사이즈를 갖고있습니다.

옛날 옛적 Bitmap에 관한 이야기
허니콤보 이전 시절, Bitmap이 Native Heap에 들어가 있었기 때문에 GC의 관리 영역 밖이였다고 합니다.
허니 콤보 이후로는 통합이 되었기 때문에, GC가 수거해갈수 있다고 하네요.


참고

[Android] 안드로이드의 SWAP

LMK란?

[Android/안드로이드] Memory Management For Android.

OOM LMK에 대한 정리

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글