이 페이지에서는 사전에 앱 내 메모리 사용량을 줄이는 방법을 설명합니다. Android 운영체제에서 메모리를 관리하는 방법에 관한 자세한 내용은 메모리 관리 개요를 참고하세요.
랜덤 액세스 메모리 (RAM)는 모든 소프트웨어 개발 환경에서 중요한 리소스이며 실제 메모리의 제약이 많은 모바일 운영체제에서는 더욱 중요합니다. Android 런타임 (ART)과 Dalvik 가상 머신에서 일상적인 가비지 컬렉션을 실행하지만, 그래도 앱에서 메모리를 할당하고 해제하는 시점과 위치를 무시할 수는 없습니다. 일반적으로 정적 멤버 변수에서 객체 참조를 유지하여 발생하는 메모리 누수를 방지하고 수명 주기 콜백에서 정의한 대로 적절한 시점에 모든 Reference 객체를 해제하는 작업은 계속해야 합니다.
앱의 코드 및 리소스 사용 공간 줄이기
코드 내의 일부 리소스와 라이브러리는 모르는 사이에 메모리를 소비할 수 있습니다. 서드 파티 라이브러리 또는 삽입된 리소스를 비롯하여 앱의 전체 크기는 앱에서 사용하는 메모리 양에 영향을 줄 수 있습니다. 코드에서 중복되거나 불필요하거나 지나치게 무거운 구성요소, 리소스, 라이브러리를 삭제하면 앱의 메모리 소비를 향상할 수 있습니다.
R8을 사용 설정하여 전체 앱 크기 줄이기
컴파일된 애플리케이션 코드는 런타임 메모리 사용량의 활성 부분입니다. 모든 클래스, 메서드, 라이브러리 종속 항목, 문자열 상수는 실행 시 RAM에 로드되어야 합니다. 컴파일된 코드베이스가 클수록 앱이 존재하기 위해 필요한 실제 RAM이 많아집니다.
R8을 사용하여 앱의 메모리 사용량을 줄일 수 있습니다. R8은 일반적으로 APK 크기 축소로 알려져 있지만 런타임 메모리 (RAM)에 직접적이고 긍정적인 영향을 미칩니다. R8은 앱의 바이트 코드를 분석하여 데드 코드를 삭제하고, 중복 클래스를 병합하고, 메서드를 인라인 처리하고, 식별자를 축소합니다. APK에서 컴파일된 바이트 코드를 RAM에 더 적게 로드하여 앱의 전체 기준 메모리 사용량을 줄입니다. 또한 클래스, 메서드, 필드 이름을 더 짧은 식별자로 축소하면 RAM 오버헤드가 직접적으로 줄어듭니다. 클래스 병합 및 광범위한 메서드 인라인 처리와 같은 최적화는 비용이 많이 드는 런타임 조회 및 할당 패턴도 대체하므로 힙 및 스택 메모리가 최적화됩니다.
보관 규칙 이해
keep 규칙은 최적화 중에 보존할 코드 부분을 R8에 알려주는 구성 명령어로, 앱에서 사용하는 코드가 삭제되거나 축소되지 않도록 합니다. 자세한 내용은 보관 규칙 개요를 참고하세요.
잘못 작성된 keep 규칙은 R8이 코드베이스의 많은 부분을 최적화하지 못하도록 합니다. 너무 광범위한 보관 규칙을 피하고 다음 권장사항을 따르세요.
- 피해야 하는 전역 규칙:
-dontoptimize: 전체 앱의 최적화를 완전히 사용 중지하여 더 크고 느린 실행 파일이 생성됩니다.-dontshrink: 사용하지 않는 코드와 리소스가 삭제되지 않도록 합니다.-dontobfuscate: 이름 축소를 방지하여 특히 대규모 앱에서 메모리 절약의 이점을 놓치게 됩니다.
패키지 전체 와일드카드 방지:
-keep class com.example.package.** { *; }와 같은 광범위한 규칙은 R8이 해당 패키지의 모든 클래스, 필드, 메서드를 보존하도록 강제합니다. 이렇게 하면 해당 패키지에서 코드를 삭제하거나 최적화하거나 축소하는 R8의 기능이 완전히 중지됩니다.기본 R8 구성 파일 사용: 항상
proguard-android-optimize.txt를 사용합니다.
보관 규칙 작성에 대한 자세한 내용은 보관 규칙 개요를 참고하세요. 사용해야 하는 특정 패턴과 사용하지 말아야 하는 특정 패턴은 보관 규칙 권장사항을 참고하세요.
R8 구성 분석기는 R8 구성과 각 유지 규칙이 앱에 미치는 영향을 파악할 수 있도록 지원합니다. 최적화를 차단하는 규칙을 식별하는 방법에 관한 자세한 내용은 R8 구성 분석기를 참고하세요.
외부 라이브러리 사용 관련 주의사항
외부 라이브러리 코드는 모바일 환경을 위해 작성되지 않는 경우가 많으므로 모바일 클라이언트에서 작업하는 데 비효율적일 수 있습니다. 외부 라이브러리를 사용하는 경우 휴대기기에 맞게 라이브러리를 최적화해야 할 수 있습니다. 이 작업을 미리 계획하고 사용하기 전에 코드 크기와 RAM이 차지하는 공간 측면에서 라이브러리를 분석하세요.
일부 모바일에 최적화된 라이브러리도 다른 구현으로 인해 문제를 일으키는 경우가 있습니다. 예를 들어, 한 라이브러리는 마이크로 protobuf를 사용하고 다른 라이브러리는 라이트 protobuf를 사용하면 결과적으로 앱에 두 가지 다른 protobuf 구현이 존재합니다. 이러한 문제는 로깅, 분석, 이미지 로드 프레임워크, 캐싱, 그 밖에 예상하지 못한 여러 곳의 다양한 구현에서 발생할 수 있습니다.
R8을 사용하여 앱을 최적화하면 종속 항목에서 사용하지 않는 코드를 삭제할 수 있지만, 라이브러리의 내부 구성에 따라 효과가 제한되는 경우가 많습니다. 예를 들어 광범위한 유지 규칙이나 라이브러리 내의 리플렉션 사용은 R8이 코드를 축소하지 못하도록 하여 메모리 사용 공간이 커질 수 있습니다. 효율적인 라이브러리 선택 전략은 라이브러리 현명하게 선택하기를 참고하세요.
수십 가지 기능 중 한두 가지만 사용하기 위해 공유 라이브러리를 사용하지 마세요. 사용하지 않는 많은 양의 코드와 오버헤드가 발생해서는 안 됩니다. 라이브러리 사용 여부를 고려할 때 필요한 내용과 확실히 일치하는 구현을 찾으세요. 또는 구현을 직접 만드는 방법도 있습니다.
종속 항목 삽입에 Hilt 또는 Dagger 2 사용
종속 항목 삽입 프레임워크는 작성하는 코드를 단순화하고 테스트 및 기타 구성 변경에 유용한 적응형 환경을 제공합니다.
앱에서 종속 항목 삽입 프레임워크를 사용하려는 경우 Hilt 또는 Dagger를 사용하는 것이 좋습니다. Hilt는 Dagger를 기반으로 실행되는 Android용 종속 항목 삽입 라이브러리입니다. Dagger는 앱 코드를 검색하는 데 리플렉션을 사용하지 않습니다. 불필요한 런타임 비용이나 메모리 사용 없이 Android 앱에서 Dagger의 정적 컴파일 시간 구현을 사용할 수 있습니다.
리플렉션을 사용하는 기타 종속성 주입 프레임워크는 코드에서 주석을 검색하여 프로세스를 초기 설정합니다. 이 프로세스에 CPU 주기와 RAM이 훨씬 더 많이 필요할 수 있으며 앱이 시작될 때 현저한 지연이 발생할 수 있습니다.
종속 항목 삽입을 사용할 때는 객체의 범위가 적절하게 지정되어 있는지 확인하여 메모리 누수를 방지해야 합니다. 잘못된 수명 주기에 바인딩하여 필요 이상으로 객체를 유지하면 메모리 누수가 발생할 수 있습니다. 자세한 내용은 범위가 지정된 객체로 메모리 누수 방지에 관한 안내를 참고하세요.
의도적으로 이미지 로드하기
그래픽 비트맵은 일반적으로 앱의 메모리에 상주하는 가장 큰 공통 객체입니다. JPEG와 같은 압축 파일로 작업하는 경우에도 화면에 표시하려면 파일을 비압축 비트맵으로 확장해야 합니다. 압축된 작은 이미지 파일이 매우 큰 비트맵으로 확장될 수 있습니다.
예를 들어 대부분의 비트맵은 ARGB_8888 구성을 사용하므로 각 픽셀에 4바이트의 메모리가 필요합니다. 빨간색, 녹색, 파란색, 알파(투명도)에 각각 1바이트가 필요합니다. 100KB JPEG가 있고 이를 1, 000×1,000픽셀 뷰에 표시하는 경우 비트맵에는 1,000, 000픽셀 각각에 4바이트가 필요하므로 메모리가 4MB 추가됩니다.
이미지 사용을 최적화하기 위해 취할 수 있는 몇 가지 조치가 있습니다. 예를 들어 이미지 로드 라이브러리를 사용하면 필요하지 않을 때 메모리를 해제할 수 있습니다. 이미지를 효율적으로 처리하는 방법에 관한 자세한 내용은 비트맵 이미지 최적화를 참고하세요.
사용 가능한 메모리 및 메모리 사용량 모니터링
앱의 메모리 사용량 문제를 해결하려면 먼저 문제를 찾아야 합니다. Android 스튜디오 메모리 프로파일러를 사용하면 다음과 같은 방법으로 메모리 문제를 찾고 진단할 수 있습니다.
- 앱에서 시간 경과에 따라 메모리를 할당하는 방법을 확인합니다. 메모리 프로파일러에서는 앱에서 사용 중인 메모리의 양, 할당된 Java 객체의 수, 가비지 컬렉션이 발생하는 시점을 실시간 그래프로 보여줍니다.
- 가비지 컬렉션 이벤트를 시작하고 앱이 실행되는 동안 Java 힙 스냅샷을 찍습니다.
- 앱의 메모리 할당을 기록하고, 할당된 객체를 모두 검사하고, 각 할당의 스택 트레이스를 확인하며, Android 스튜디오 편집기에서 해당 코드로 이동합니다.
메모리 프로파일러는 LeakCanary 누수 감지 라이브러리와도 통합됩니다. LeakCanary를 사용하면 메모리 누수 분석을 테스트 기기에서 개발 머신으로 이동할 수 있으므로 워크플로 속도를 크게 높일 수 있습니다. 자세한 내용은 Android 스튜디오 출시 노트를 참고하세요.
프로덕션 앱을 실행하는 사용자의 데이터를 기반으로 메모리 문제를 진단하는 데 사용할 수 있는 다른 도구가 있습니다.
- Android Vitals를 사용하여 메모리 부족 종료 (LMK) 이벤트를 추적합니다.
- 프로파일링 관리자를 사용하여 메모리 부족 오류와 메모리 누수로 인해 발생할 수 있는 비정상적인 앱 동작을 추적합니다.
이벤트에 대한 응답으로 메모리 해제
메모리 관리 개요에 설명된 대로 Android는 중요한 작업을 위한 메모리를 확보하기 위해 필요한 경우 앱에서 메모리를 회수하거나 앱을 완전히 중지할 수 있습니다. 시스템 메모리의 균형을 유지하고 시스템에서 앱 프로세스를 중지하지 않도록 하려면 Activity 클래스에 ComponentCallbacks2 인터페이스를 구현하면 됩니다. 제공된 onTrimMemory() 콜백 메서드는 앱이 자발적으로 메모리 사용량을 줄일 수 있는 좋은 기회를 제공하는 수명 주기 또는 메모리 관련 이벤트를 앱에 알립니다.
메모리를 확보하면 로우 메모리 킬러에 의해 앱이 종료되는 빈도를 줄일 수 있습니다.
onTrimMemory() 구현은 TRIM_MEMORY_UI_HIDDEN 및 TRIM_MEMORY_BACKGROUND 이벤트에만 집중해야 합니다. (Android 14부터 시스템은 다른 기존 상수에 대한 알림을 더 이상 전송하지 않습니다. 이러한 상수는 Android 15에서 공식적으로 지원 중단되었습니다.)
TRIM_MEMORY_UI_HIDDEN: 이 신호는 앱의 UI가 사용자의 뷰에서 전환되었음을 나타냅니다. 이 전환은 비트맵, 동영상 재생 버퍼, 복잡한 애니메이션 리소스 등 UI에만 엄격하게 연결된 상당한 메모리 할당을 해제할 기회를 제공합니다.TRIM_MEMORY_BACKGROUND: 이 신호는 프로세스가 백그라운드에 상주하며 이제 시스템의 전역 메모리 요구사항을 충족하기 위해 종료될 수 있음을 나타냅니다. 프로세스가 캐시된 상태로 유지되는 시간을 늘리고 앱 콜드 스타트 수를 줄이려면 사용자가 세션을 재개한 후 쉽게 재구성할 수 있는 리소스를 적극적으로 해제해야 합니다.
이 코드 샘플은 onTrimMemory() 콜백을 구현하여 다양한 메모리 관련 이벤트에 응답하는 방법을 보여줍니다.
Kotlin
import android.content.ComponentCallbacks2
// Other import statements.
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
import android.content.ComponentCallbacks2;
// Other import statements.
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
public void onTrimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
필요한 메모리 용량 확인
Android에서는 여러 개의 프로세스 실행을 허용하기 위해 각 앱에 할당된 힙 크기를 엄격하게 제한합니다. 정확한 힙 크기 제한은 기기에서 전체적으로 사용할 수 있는 RAM 크기를 기준으로 기기에 따라 다릅니다. 앱이 힙 용량에 도달하여 더 많은 메모리를 할당하려고 하면 시스템에서 OutOfMemoryError가 발생합니다.
메모리 부족을 방지하려면 시스템에 쿼리하여 현재 기기에서 사용할 수 있는 힙 공간의 양을 확인하세요. getMemoryInfo()를 호출하여 시스템에 이 수치를 쿼리할 수 있습니다. 이 메서드는 사용 가능한 메모리, 총 메모리, 메모리 기준점(시스템에서 프로세스를 중지하기 시작하는 메모리 수준)과 같은 기기의 현재 메모리 상태 정보를 제공하는 ActivityManager.MemoryInfo 객체를 반환합니다. 또한 ActivityManager.MemoryInfo 객체는 기기의 메모리가 부족한지 알려주는 간단한 불리언인 lowMemory를 노출합니다.
다음 코드 스니펫 예는 앱에서 getMemoryInfo() 메서드를 사용하는 방법을 보여줍니다.
Kotlin
fun doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
if (!getAvailableMemory().lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return ActivityManager.MemoryInfo().also { memoryInfo ->
activityManager.getMemoryInfo(memoryInfo)
}
}
자바
public void doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if (!memoryInfo.lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}
메모리 부족으로 인한 종료 모니터링
사용자에게 표시되는 메모리 부족 종료 (LMK)는 시스템 메모리가 심각하게 부족해질 때 발생합니다. 메모리가 부족하면 lmkd (로우 메모리 킬러 데몬)가 oom_adj_score에 따라 프로세스를 종료합니다. 캐시되거나 연결된 UI가 없는 서비스 (예: 작업)를 실행하는 앱은 점수가 가장 높으며 먼저 종료됩니다. 메모리가 심각하게 부족한 상태로 유지되면 데몬은 oom_adj_score이 0인 프로세스에서 메모리를 강제로 회수합니다.
이 점수는 표시되는 앱에 예약되어 있으므로 이러한 앱이 종료되면 즉시 비정상적인 프로세스 종료가 발생합니다. 최종 사용자에게는 앱이 비정상 종료된 것처럼 보이며, 표준 수명 주기 상태 저장 메커니즘을 우회하여 사용자 진행 상황이 손실되는 경우가 많습니다.
포그라운드 프로세스 종료는 메모리 관리 오류의 충실도가 높은 프록시 역할을 하므로 Android Vitals에서 주요 관심사입니다. 1% 보다 높은 LMK 비율은 즉각적인 조치가 필요함을 나타내지만 낮은 비율이 반드시 양호한 상태를 나타내는 것은 아닙니다. 사용자 인식 LMK 발생률이 낮다는 것은 LMK 데몬이 백그라운드에 있는 프로세스를 자주 종료하여 '웜 스타트' 성능과 멀티태스킹 유연성이 저하된다는 의미일 수 있습니다. 따라서 장기적인 안정성과 기기 상태를 보장하기 위해 현재 LMK 점수와 관계없이 메모리 권장사항을 준수하는 것이 좋습니다.
ProfilingManager를 사용하여 메모리 문제 추적
Android 플랫폼은 설정한 트리거를 기반으로 프로덕션에서 사용자 데이터를 캡처할 수 있는 고급 관측 가능성 API인 ProfilingManager를 제공합니다. 이렇게 하면 재현하기 어려운 메모리 문제를 식별하는 데 도움이 될 수 있습니다.
Android 17에서 도입된 두 가지 새로운 트리거는 특히 메모리 문제를 발견하는 데 유용합니다.
TRIGGER_TYPE_OOM은 앱이OutOfMemoryError를 발생시켰음을 나타냅니다. 앱이 프로파일링 트리거를 등록할 때 비정상 종료 후 앱이 시작될 때 트리거됩니다.TRIGGER_TYPE_ANOMALY는 시스템에서 앱의 비정상적인 동작을 감지할 때 트리거됩니다. 과도한 메모리 사용량으로 인해 트리거될 수 있습니다. 앱이 과도한 메모리 사용량을 보인 후, 시스템이 문제가 있는 프로세스를 중지하기 위한 조치를 취하기 전에 트리거됩니다. 예를 들어 앱이 Android 17에 도입된 메모리 제한을 초과하면 시스템이 앱을 종료하기 전에TRIGGER_TYPE_ANOMALY이 트리거됩니다.
ProfilingManager를 사용하여 프로그래매틱 방식으로 트리거를 등록하고 검색하는 방법에 관한 자세한 내용은 트리거 기반 프로파일링 문서를 참고하세요.
앱 기반 프로파일링을 사용하여 추적 시작 및 종료 지점을 수동으로 정의할 수도 있습니다. 메모리 누수나 과도한 메모리 사용이 의심되는 영역에서 힙 덤프나 힙 프로필을 수동으로 캡처하는 것이 좋습니다.
메모리 효율성이 높은 코드 구성 사용
일부 Android 기능, Java 클래스, 코드 구성은 다른 기능, 클래스, 구성에 비해 메모리를 더 많이 사용합니다. 코드에서 효율성이 높은 대안을 선택하면 앱에서 사용하는 메모리의 양을 최소화할 수 있습니다.
서비스를 드물게 사용
불필요하면 서비스를 실행 상태로 두지 않는 것이 좋습니다. 불필요한 서비스를 계속 실행 상태로 두는 것은 Android 앱에서 일으킬 수 있는 최악의 메모리 관리 실수 중 하나입니다. 앱이 백그라운드에서 작동하는 서비스가 필요한 경우 작업을 실행해야 하는 경우가 아니면 서비스를 실행 상태로 두지 마세요. 작업이 완료되면 서비스를 중지합니다. 그러지 않으면 메모리 누수가 발생할 수 있습니다.
서비스를 시작하면 시스템에서는 해당 서비스의 프로세스를 실행 상태로 유지하려고 합니다. 이 동작으로 인해 서비스에서 사용하는 RAM을 다른 프로세스에서 사용할 수 없기 때문에 서비스 프로세스의 비용이 매우 높아집니다. 이로 인해 시스템에서 LRU 캐시에 유지할 수 있는 캐시된 프로세스의 수가 줄어들어 앱 전환의 효율성이 저하됩니다. 심지어 메모리가 부족하고 시스템에서 현재 실행 중인 모든 서비스를 호스팅하기에 충분한 프로세스를 유지할 수 없게 되면 시스템에서 스래싱이 발생할 수 있습니다.
일반적으로 지속적인 서비스 사용은 사용 가능한 메모리를 끊임없이 요구하므로 피하는 것이 좋습니다. 대신 WorkManager와 같은 대체 구현을 사용하는 것이 좋습니다.
WorkManager를 사용하여 백그라운드 프로세스를 예약하는 방법에 관한 자세한 내용은 지속적인 작업을 참고하세요.
최적화된 데이터 컨테이너 사용
프로그래밍 언어에서 제공하는 일부 클래스는 휴대기기에서 사용하도록 최적화되지 않았습니다. 예를 들어, 일반 HashMap 구현은 모든 매핑에 별도의 항목 객체가 필요하므로 메모리 사용이 비효율적일 수 있습니다.
Android 프레임워크에는 SparseArray, SparseBooleanArray, LongSparseArray를 비롯하여 여러 최적화된 데이터 컨테이너가 포함되어 있습니다. 예를 들어, SparseArray 클래스는 키와 때로는 값(항목당 한두 개의 객체 생성)을 시스템에서 오토박싱하지 않으므로 더 효율적입니다.
필요한 경우 언제든지 가벼운 데이터 구조를 위해 원시 배열로 전환할 수 있습니다.
코드 추상화 관련 주의사항
개발자는 코드 유연성과 유지관리를 개선할 수 있어 추상화를 좋은 프로그래밍 관행으로 사용하는 경우가 많습니다. 그러나 추상화에는 일반적으로 실행해야 하는 코드가 더 많이 필요합니다. 앱의 코드 및 리소스 설치 공간 줄이기에 자세히 설명된 대로 컴파일된 코드베이스가 클수록 앱에 필요한 실제 RAM이 직접적으로 증가합니다. 추상화가 크게 유용하지 않다면 사용하지 마세요.
직렬화된 데이터에 라이트 protobuf 사용
프로토콜 버퍼(protobuf)는 Google에서 구조화된 데이터 직렬화를 위해 설계한 언어 및 플랫폼 중립적인 확장 가능한 메커니즘으로, XML과 비슷하지만 더 작고 빠르며 단순합니다. 데이터에 protobuf를 사용하는 경우 항상 클라이언트 측 코드에서 라이트 protobuf를 사용하세요. 일반 protobuf는 지나치게 상세한 코드를 생성하므로 RAM에서 앱의 코드 설치 공간이 증가하고 (앱의 코드 설치 공간 관리 및 최적화 참고) APK 크기가 증가합니다.
자세한 내용은 protobuf 리드미를 참고하세요.
메모리 누수에 주의
참조 관리가 부적절하면 객체가 유용한 수명을 초과하여 메모리 누수가 발생할 수 있으며, 이로 인해 가비지 컬렉터가 누수된 객체의 메모리를 회수하지 못하게 됩니다. 메모리 누수를 방지하려면 수명 주기 인식 설계를 구현하세요.
자세한 내용은 메모리 누수를 참고하세요.
메모리 급변 방지
가비지 컬렉션 이벤트는 앱 성능에 영향을 주지 않습니다. 하지만 단기간에 많은 가비지 컬렉션 이벤트가 발생하면 가비지 컬렉터와 앱 스레드 사이에 필요한 상호작용으로 인해 배터리가 빠르게 소모되고 프레임을 설정하는 데 걸리는 시간이 미미하게나마 늘어날 수 있습니다. 시스템에서 가비지 컬렉션에 소비하는 시간이 길어질수록 배터리가 빠르게 소모됩니다.
메모리 급변으로 인해 수많은 가비지 컬렉션 이벤트가 발생하는 경우가 많습니다. 실제로 메모리 급변은 특정 기간 내에 발생하는 할당된 임시 객체의 수를 나타냅니다.
예를 들어, for 루프 내에 여러 임시 객체를 할당할 수 있습니다.
또는 뷰의 onDraw() 함수 내에 새 Paint 또는 Bitmap 객체를 생성할 수도 있습니다. 두 경우 모두 앱에서 많은 객체를 빠르게 대량으로 만듭니다. 이로 인해 새로운 객체 영역에서 사용 가능한 모든 메모리가 빠르게 소모되어 가비지 컬렉션 이벤트를 발생시킬 수 있습니다.
문제를 해결하기 전에 메모리 프로파일러를 사용하여 코드에서 메모리 급변이 높은 위치를 찾으세요.
코드에서 문제 영역을 확인한 후에는 성능이 중요한 영역 내에 할당 수를 줄여 보세요. 또한 내부 루프에서 객체를 이동하거나 factory 기반 할당 구조로 객체를 이동하는 것이 좋습니다.
객체 풀이 사용 사례에 도움이 되는지 평가할 수도 있습니다. 객체 풀을 사용하면 더 이상 필요하지 않은 객체 인스턴스를 삭제하는 대신 풀로 해제합니다. 다음번에 이 유형의 객체 인스턴스가 필요할 때 이를 할당하는 대신 풀에서 가져올 수 있습니다.
성능을 철저히 평가하여 특정 상황에서 객체 풀이 적합한지 확인합니다. 객체 풀을 사용함으로써 성능이 더 악화되는 경우도 있습니다. 풀을 사용하면 할당이 방지되지만 그 밖의 오버헤드가 발생합니다. 예를 들어, 풀을 유지관리하려면 보통 동기화가 진행되는데, 이 과정에서 무시할 수 없는 오버헤드가 발생합니다. 또한 해제 중에 메모리 누수를 방지하기 위해 풀링된 객체 인스턴스를 지운 다음 획득 중에 인스턴스를 초기화하는 과정에서도 0이 아닌 오버헤드가 발생할 수 있습니다.
풀에 필요한 것보다 많은 객체 인스턴스를 보유하는 것도 가비지 컬렉션에 부담이 됩니다. 객체 풀은 가비지 컬렉션 호출 수를 줄이지만 라이브 (연결 가능한) 바이트 수에 비례하므로 모든 호출에 필요한 작업량은 증가하게 됩니다.