Android 런타임(ART)과 Dalvik 가상 머신은 페이징과 메모리 매핑(mmapping)을 사용하여 메모리를 관리합니다. 즉, 새로운 개체를 할당해서든 메모리 매핑된 페이지를 터치해서든 앱이 수정하는 모든 메모리는 RAM에 계속 유지되며 페이지 아웃될 수 없습니다. 앱에서 메모리를 해제하는 유일한 방법은 앱이 보유한 개체 참조를 해제하여 가비지 컬렉터에서 메모리를 사용하도록 하는 것입니다. 한 가지 예외가 있습니다. 시스템이 메모리를 다른 데 사용하려는 경우 코드와 같이 수정 없이 메모리 매핑된 모든 파일이 RAM에서 페이지 아웃될 수 있습니다.
한마디로 안드로이드 기기에서 구동되는 자바 런타임(Dalvik 이나 ART)은 메모리 관리 환경입니다. 일반적으로 런타임에서 모든 메모리 할당과 해제(GC)를 처리합니다.
Android는 프로세스 간에 RAM 페이지를 공유하려고 합니다.
그러면 뭐가 필요하나? 여기서 짚고 넘어갈 개념 중 하나인 Zygote라는 프로세스입니다.
Zygote 는 프레임워크 클래스, 리소스, 네이티브 라이브러리를 미리 로딩해서 가지고 있는 프로세스입니다.
앱이 시작되면 앱의 개별 코드를 로딩하기 전에 먼저
Zygote
프로세스를 복제합니다. 복제된 프로세스들은 프레임워크 클래스, 리소스, 네이티브 라이브러리를 공유해서 사용하게 됩니다. 그렇기 때문에 앱은 아무런 준비 없이 로딩되는 것보다 훨씬 빠르게 시작할 수 있습니다.
정적 데이터의 예로는 Dalvik 코드(직접 메모리 매핑을 위해 사전 연결된 .odex 파일에 배치), 앱 리소스(리소스 테이블을 메모리 매핑될 수 있는 구조로 설계 및 APK의 zip 항목 정렬), .so 파일의 네이티브 코드와 같은 기본 프로젝트 요소 등이 있습니다.
앞서 설명한 Zygote 프로세스처럼 모든 앱에서 공통으로 사용하는 프레임워크 클래스, 리소스, 네이티브 라이브러리 등이 있습니다.
안드로이드는 메모리를 절약하려고 이런 것을 공유 메모리에 올려두고 앱 사이에서 함께 사용합니다.
공유 메모리는 메모리 사용량을 분석할 때 프로세스에 균등하게 1/n 으로 나누어 적용합니다.
전용 메모리는 특정 앱 내부에서 사용되고 다른 앱에서 사용할 수 없는 영역입니다.
Dalvik 힙은 각 앱 프로세스의 단일 가상 메모리 범위로 제한됩니다. 이것은 논리적 힙 크기를 정의하며 이 힙 크기는 필요에 따라 시스템이 각 앱에 정의하는 한도까지만 커질 수 있습니다.
힙의 논리적 크기는 힙에서 사용하는 실제 메모리 양과 동일하지 않습니다. 앱의 힙을 검사할 때 Android는 PSS(Proportional Set Size)라는 값을 계산합니다. PSS는 다른 프로세스와 공유되는 더티 페이지와 클린 페이지를 일정 부분 모두 차지하지만, RAM을 공유하는 앱 수에 비례하는 정도로만 차지합니다. 이 PSS 총계는 시스템에서 실제 메모리 공간으로 간주됩니다.
PSS외에 2가지 종류가 더 있습니다.
- RSS : Resident Set Size = Private 더티 RAM + Shared RAM 전체
- USS : Unique Set Size = RSS - shared RAM 전체 = Private Dirty RAM만 대상
(feat. os 속 페이징)
페이징 기법(paging)은 컴퓨터가 메인 메모리에서 사용하기 위해 2차 기억 장치[a]로부터 데이터를 저장하고 검색하는 메모리 관리 기법이다.[1]
즉 가상기억장치를 모두 같은 크기의 블록으로 편성하여 운용하는 기법이다. 이때의 일정한 크기를 가진 블록을 페이지(page)라고 한다.
ART 런타임의 가장 큰 특징은 설치할 때 앱이 컴파일(AOT, Ahead Of Time) 된다는 것입니다.
반면 Dalvik 런타임은 앱이 구동되는 사이마다 자주 실해앟는 부분만을 컴파일하는 JIT(Just In Time)방식을 사용합니다.
ART가 구동되는 기기에서는 앱 코드가 모두 설치 시점에 컴파일되어 디스크에 저장됩니다.
이는 클린 영역이라 여겨지고, 디스크로부터 복구가 쉽기 때문에 메모리 부족 시 메모리에서 쉽게 제거할 수 있습니다.
ART를 쓴 이후로 메모리 내에 로딩된 앱의 코드를 클린 메모리로 판단해 ART 에서의 메모리 관리는 더욱 향상되었다고 볼 수 있습니다.
더티 메모리는 데이터가 램 영역에만 저장되어 있습니다. 램에서 제거되면 데이터를 다시 얻기 위해 앱이 재실행되어야 합니다.
클린 메모리는 랩에 올라와 있는 데이터가 이미 디스크에도 동일하게 저장되어 있는 상태입니다. 클린 메모리가 제거된 후에 다시 기기로부터 그대로 읽기만 하면 된다.
시스템은 앱을 닫힌 후에도 메모리에 보관하여 사용자가 앱으로 빠르게 다시 전환할 수 있게 합니다. 이러한 이유로 Android 기기는 사용 가능한 메모리가 거의 없는 상태로 실행되는 경우가 많습니다. 메모리 관리는 중요한 시스템 프로세스와 여러 사용자 애플리케이션 사이에서 메모리를 올바르게 할당하는 데 매우 중요합니다.
Android 기기에는 3가지 메모리 유형이 있습니다.
가장 빠른 메모리 유형이지만 일반적으로 크기가 제한됩니다. 고급형 기기에는 일반적으로 최대 크기의 RAM이 있습니다.
RAM의 파티션으로 스왑 공간에 사용됩니다. 모든 것은 zRAM에 배치될 때 압축되고 zRAM에서 복사될 때 압축이 해제됩니다. 이 부분의 RAM은 페이지가 zRAM으로 들어오거나 zRAM에서 나갈 때 크기가 커지거나 작아집니다. 기기 제조업체가 최대 크기를 설정할 수 있습니다.
파일 시스템, 모든 앱 및 라이브러리, 플랫폼에 포함된 개체 코드와 같은 영구 데이터가 모두 포함됩니다. 저장소는 다른 두 메모리 유형보다 용량이 훨씬 큽니다. Android에서는 저장소가 다른 Linux 구현에서처럼 스왑 공간에 사용되지 않습니다. 빈번한 쓰기 작업으로 메모리에 마모가 발생하고 저장소 매체의 수명을 단축할 수 있기 때문입니다.
RAM은 페이지로 나뉩니다. 일반적으로 각 페이지는 4KB 메모리입니다.
페이지는 사용 가능하거나 사용된 것으로 간주됩니다. 사용 가능한 페이지는 사용되지 않은 RAM입니다. 사용된 페이지는 시스템에서 활발히 사용 중인 RAM이며 다음 카테고리로 그룹화됩니다.
kswapd : 커널 스왑 데몬의 줄임말
Private : 하나의 프로세스에서 소유하고 공유되지 않음
Clean : 저장소에 있는 파일의 수정되지 않은 복사본. kswapd
로 삭제하여 사용 가능한 메모리를 늘릴 수 있습니다.
Dirty : 저장소에 있는 파일의 수정된 복사본. kswapd
를 통해 zRAM으로 이동하거나 zRAM에서 압축
하여 사용 가능한 메모리를 늘릴 수 있습니다.
Shared : 여러 프로세스에서 사용
kswapd
로 삭제하여 사용 가능한 메모리를 늘릴 수 있습니다.kswapd
를 통해 msync()나 munmap()
을 명시적으로 사용하여 저장소에 있는 파일에 변경사항을 다시 써서 사용 가능한 메모리를 늘릴 수 있습니다.kswapd
를 통해 zRAM으로 이동하거나 zRAM에서 압축하여
사용 가능한 메모리를 늘릴 수 있습니다.Android에는 메모리 부족 상황을 처리하는 두 가지 기본 메커니즘인 커널 스왑 데몬
과 로우 메모리 킬러
가 있습니다.
Linux 커널의 일부이며 사용된 메모리를 사용 가능한 메모리로 변환합니다. 데몬은 기기의 사용 가능한 메모리가 부족해질 때 활성화됩니다.
Linux 커널은 사용 가능한 메모리의 낮은 임계값과 높은 임계값을 유지합니다.
kswapd
가 메모리 회수를 시작.kswapd
가 메모리 회수를 중지.kswapd
는 저장소로 지원되고 수정되지 않았으므로 클린 페이지를 삭제하여 회수할 수 있습니다. 프로세스가 삭제된 클린 페이지를 처리하려고 하면 시스템은 페이지를 저장소에서 RAM으로 복사합니다. 이 작업을 요구 페이징
이라고 합니다.
kswapd
는 캐시된 비공개 더티 페이지와 익명의 더티 페이지를 zRAM으로 이동할 수 있고 zRAM에서 이동한 페이지가 압축됩니다.
이렇게 하면 RAM에서 사용할 수 있는 메모리(사용 가능한 페이지)가 확보됩니다.
프로세스가 zRAM의 더티 페이지를 터치하려고 하면 페이지가 압축 해제되고 다시 RAM으로 이동합니다. 압축된 페이지와 연결된 프로세스가 종료되면 페이지가 zRAM에서 삭제됩니다.
사용 가능한 메모리 양이 특정 임계값 아래로 떨어지면 시스템에서는 프로세스를 종료하기 시작합니다.
kswapd
로는 시스템에 충분한 메모리를 확보할 수 없는 경우가 많습니다.
이 경우 시스템은 onTrimMemory()
를 사용하여 메모리가 부족하고 할당량을 줄여야 한다고 앱에 알립니다.
이 방법으로 충분하지 않으면 커널이 메모리를 확보하려고 프로세스를 종료하기 시작합니다. 이 작업을 실행하기 위해 로우 메모리 킬러(low-memory killer, LMK)를 사용합니다.
LMK는 oom_adj_score
라는 '메모리 부족' 점수를 사용하여 실행 중인 프로세스의 우선순위를 정합니다. 최고 점수를 얻은 프로세스가 먼저 종료됩니다. 백그라운드 앱이 먼저, 시스템 프로세스가 마지막에 종료됩니다.
Android에서는 여러 가지 방법으로 앱에서 메모리를 회수하거나 필요한 경우 앱을 완전히 종료시켜 중요한 작업을 위한 메모리를 확보합니다.
시스템 메모리의 균형을 유지하고 시스템에서 앱 프로세스를 종료하지 않도록 하려면 Activity
클래스에 ComponentCallbacks2
인터페이스를 구현하면 됩니다. 제공된 onTrimMemory()
콜백 메서드를 사용하면 앱이 포그라운드 또는 백그라운드에 있을 때 앱에서 메모리 관련 이벤트를 수신 대기할 수 있고 그런 다음 앱 수명 주기 또는 시스템의 메모리 회수 요구를 나타내는 시스템 이벤트의 응답으로 객체를 해제하면 됩니다.
https://stackoverflow.com/questions/20797338/how-to-clear-inactive-memory-in-android-programmatically
https://stackoverflow.com/questions/28103812/want-to-clean-unused-memory-from-ram-of-unrooted-android-device-programatically
public void freeMemory(){
System.runFinalization();
Runtime.getRuntime().gc();
System.gc();
}
https://developer.android.com/topic/performance/memory?hl=ko
https://developer.android.com/topic/performance/memory-overview?hl=ko
https://aroundck.tistory.com/5290
https://developer.android.com/topic/performance/memory-management#kswapd