LeakCanary — 무엇인가, Android 누수 탐색 라이브러리

저자: IT Sectr 게시일: 2026-03-30 읽는 시간: 9 분

LeakCanary는 Square의 오픈소스 라이브러리로, Android 애플리케이션에서 메모리 누수를 자동으로 감지합니다. 개발 프로세스에 통합되어 Activity, Fragment, ViewModel 및 기타 구성 요소의 라이프사이클을 실시간으로 모니터링하고 누수가 발생하자마자 신호를 보냅니다. Square Open Source에 따르면, 이 라이브러리는 수천 개의 프로젝트에서 사용되며 Android 메모리 진단의 사실상 표준으로 간주됩니다.

핵심 요점

  • LeakCanary는 Android에서 자동 메모리 누수 감지를 위한 라이브러리입니다.
  • 작동 메커니즘은 WeakReference와 구성 요소 파괴 후 수동 GC 트리거에 기반합니다.
  • 힙 덤프는 누수 감지 시 자동으로 생성되고 내장 분석기에 의해 분석됩니다.
  • 결과 — 코드 내 누수 위치를 가리키는 정확한 참조 체인(누수 추적).
  • LeakCanary 2.x는 수동 설정이 필요 없음 — build.gradle에 종속성 하나만 추가하면 됩니다.

LeakCanary란?

LeakCanary는 Square가 개발한 Android 애플리케이션의 자동 메모리 누수 감지 라이브러리입니다. 앱 빌드 프로세스에 통합되어 파괴되어야 하는 객체(Activity, Fragment, View)가 메모리에 남아 있는지 자동으로 모니터링합니다. 누수가 감지되면 LeakCanary는 힙 덤프를 생성하고 객체를 보유하는 참조 체인을 분석합니다.

이 라이브러리는 Android 커뮤니티의 표준이 되었습니다. GitHub에 따르면, 프로젝트는 28,000개 이상의 스타를 보유하고 있으며 Google, Uber, Airbnb, Facebook의 애플리케이션에서 사용됩니다. LeakCanary는 두 가지 주요 버전으로 제공됩니다: 클래식 1.x(수동 설정)와 최신 2.x(ContentProvider를 통한 자동 통합)입니다. 버전 2.x는 Application 클래스 수정이 필요 없으며, 종속성만으로 완전한 기능을 사용할 수 있습니다.

LeakCanary의 주요 작업은 객체의 라이프사이클이 종료된 후에도 메모리에 계속 존재하는 상황을 감지하는 것입니다. 이는 정적 필드, 싱글톤, 등록되지 않은 콜백, 익명 클래스 및 외부 객체를 캡처하는 클로저를 통한 누수에 일반적입니다.

Android 개발에서 LeakCanary가 중요한 이유

모바일 기기의 제한된 RAM으로 인해 Android의 메모리 누수는 데스크톱보다 더 심각합니다. 각 화면 전환 시 5~10MB의 누수라도 앱을 30~40분 사용하면 OutOfMemoryError로 이어질 수 있습니다. LeakCanary는 프로덕션에서의 충돌을 기다리지 않고 개발 단계에서 이러한 문제를 감지합니다.

LeakCanary의 작동 방식

LeakCanary는 약한 참조(WeakReference)와 강제 가비지 수집을 결합하여 사용합니다. Activity 또는 Fragment가 onDestroy를 호출하면 LeakCanary는 해당 객체에 대한 WeakReference를 생성하고 짧은 지연(기본 5초) 후에 GC를 트리거합니다. GC 후에도 객체가 WeakReference를 통해 접근 가능하다면, 강한 참조에 의해 유지되고 있는 것이므로 누수로 기록됩니다.

누수를 감지한 후 LeakCanary는 힙 덤프(메모리 덤프)를 생성합니다 — HPROF 형식의 앱 메모리 전체 스냅샷입니다. 그런 다음 내장 분석기(버전 2.x의 경우 Shark)가 GC Roots에서 누수 객체까지의 도달 가능성 그래프를 구축하고 최단 경로 — 객체를 메모리에 유지하는 참조 체인을 찾습니다.

kotlin
// LeakCanary 감지 로직 단순화
class ObjectWatcher {
    private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()

    fun watch(watchedObject: Any, description: String) {
        val reference = KeyedWeakReference(watchedObject, description)
        watchedReferences.add(reference)
        BackgroundHandler.postDelayed({
            checkForLeaks()
        }, 5000)
    }

    private fun checkForLeaks() {
        GcTrigger.runGc() // 강제 GC
        for (ref in watchedReferences) {
            if (ref.get() != null) {
                onLeakFound(ref) // 객체가 GC에서 살아남았습니다 — 누수입니다
            }
        }
    }
}

핵심은 GcTrigger.runGc()의 강제 호출입니다. 이것 없이는 실제로 누수된 객체와 GC가 아직 수집하지 않은 객체를 구분할 수 없습니다. LeakCanary는 최대 세 번까지 이 작업을 수행합니다: 세 번의 GC 사이클 후에도 객체가 메모리에 남아 있으면 누수가 확인됩니다.

Shark란 — 힙 덤프 분석기

Shark는 LeakCanary 2.x에 내장된 힙 덤프 분석기로, Kotlin으로 작성되었습니다. 이전 HAHA 분석기와 달리 Shark는 HPROF 파일 전체를 메모리에 로드하지 않고 최소 할당으로 객체 그래프를 탐색합니다. 이를 통해 분석 중 RAM 소비를 50MB에서 2~5MB로 줄이고 분석 시간을 30초에서 1~3초로 단축합니다.

LeakCanary 설치 및 설정 방법

최신 Android 프로젝트에 LeakCanary 2.x를 설치하는 데는 build.gradle에 한 줄만 추가하면 됩니다. 라이브러리는 ContentProvider를 사용하여 자동으로 초기화되므로 Application 클래스를 수정하거나 MainActivity에 코드를 추가할 필요가 없습니다. 릴리스 APK에 불필요한 코드가 포함되지 않도록 종속성은 debug 빌드에만 추가됩니다.

groovy
// build.gradle (app/module)
dependencies {
    // debugImplementation — 디버그 빌드 전용 라이브러리
    debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}

종속성을 추가하고 프로젝트를 다시 빌드하면 LeakCanary가 자동으로 앱에 나타납니다. 첫 실행 시 라이브러리는 활성화를 확인하는 시스템 알림을 표시합니다. 감지된 모든 누수는 알림으로 표시되며, 알림을 탭하면 상세 보고서(LeakTrace)가 있는 화면이 열립니다.

사용자 지정을 위해 자체 AppWatcherInstaller를 만들고 매개변수를 재정의할 수 있습니다: GC 타임아웃, 추적할 객체 유형 목록, 디스크에 힙 덤프 저장 활성화 등. 그러나 90%의 프로젝트에서는 기본 구성이 최적입니다.

코루틴 및 Jetpack Compose 설정

버전 2.12부터 LeakCanary는 ViewModel, 코루틴 범위 및 Compose State 객체의 자동 추적을 지원합니다. 추가 종속성이 필요하지 않습니다 — 라이브러리가 프로젝트에서 사용 중인 Jetpack 구성 요소를 자동으로 감지하고 해당 감지기를 활성화합니다.

LeakCanary 보고서 읽는 방법

LeakCanary 보고서(LeakTrace)는 GC Root에서 누수 객체까지의 여러 줄로 된 참조 체인입니다. 각 줄은 강한 참조가 통과하는 클래스와 필드를 보여줍니다. 개발자는 체인을 아래에서 위로 읽어야 합니다: 맨 아래 줄은 누수 객체, 맨 위 줄은 진입점(GC Root)입니다.

일반적인 LeakTrace는 다음과 같습니다: GC Root → Application의 정적 필드 → 싱글톤 → 콜백 → Activity. 개발자가 이러한 체인을 보면 문제가 명확합니다: 싱글톤이 Activity에 대한 참조를 캡처한 콜백을 보유하고 있습니다. 해결책은 싱글톤에서 강한 참조를 약한 참조로 바꾸는 것입니다.

text
┬
├─ android.app.Application
│    Leaking: NO (Application — singleton)
│    ↓ Application.leakedActivities
├─ java.util.ArrayList
│    Leaking: NO (ArrayList — normal)
│    ↓ ArrayList[0]
├─ com.example.MainActivity
│    Leaking: YES (Activity destroyed but still in memory)
│    ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│    Leaking: UNKNOWN
│    ↓ CallbackWrapper.mListener
│              ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│    Leaking: UNKNOWN
│    ↓ MyCallback.this$0
├─ com.example.MainActivity
│    Leaking: YES (MainActivity is the leak)
╰

이 예제에서 LeakCanary는 MainActivity가 체인을 통해 유지되고 있음을 보여줍니다: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → 다시 MainActivity. this$0 화살표는 익명 클래스 MyCallback이 Activity에 대한 외부 참조를 캡처했음을 나타냅니다. 해결책은 콜백을 약한 참조로 만들거나 onDestroy에서 취소하는 것입니다.

LeakCanary는 체인의 각 요소에 대한 누수 상태도 표시합니다: NO(누수 없음 — 루트 요소), YES(객체를 파괴해야 함), UNKNOWN(상태를 확인할 수 없음). UNKNOWN 상태는 문제를 의미하지 않습니다 — LeakCanary가 명확하게 분류할 수 없는 중간 객체입니다.

LeakCanary 2.x vs 1.x: 주요 차이점

버전 1.x에서 2.x로의 전환은 근본적이었습니다: 개발자들은 라이브러리를 처음부터 다시 작성하여 오래된 HAHA 분석기를 Kotlin으로 작성된 자체 엔진 Shark로 교체했습니다. Shark는 훨씬 빠르고, 분석에 필요한 메모리가 적으며, 누수의 근본 원인을 더 정확하게 찾아냅니다.

매개변수LeakCanary 1.xLeakCanary 2.x
분석기 언어Java (HAHA — Android SDK 포크)Kotlin (Shark — 자체 엔진)
설치Application에서 수동 AppWatcher 설정ContentProvider를 통해 자동
속도힙 덤프 분석에 10~30초힙 덤프 분석에 1~5초
성능분석 중 10~50MB RAM 사용분석 중 2~10MB RAM 사용

Shark의 주요 장점은 힙 덤프 전체를 메모리에 로드하지 않고 최소 할당으로 참조 그래프를 탐색한다는 것입니다. 이로 인해 LeakCanary 2.x는 분석 중 OutOfMemoryError 위험 없이 RAM이 적은 기기에서도 사용할 수 있습니다.

버전 2.x에서는 Android Studio Memory Profiler에서 나중에 분석할 수 있도록 힙 덤프를 파일로 내보내기 기능도 도입되었습니다. 이렇게 하려면 AppWatcher 구성에서 dumpHeapWhenLeakFound 설정을 활성화하세요.

LeakCanary가 찾는 일반적인 누수

LeakCanary는 Android에서 흔한 여러 유형의 누수를 효과적으로 감지합니다. 가장 빈번한 것은 Activity에 대한 정적 참조를 통한 누수입니다 — 개발자가 싱글톤에 Activity 컨텍스트에 대한 참조를 유지하여 Activity가 라이프사이클 종료 후 GC에 의해 수집되지 못하는 경우입니다.

두 번째로 흔한 범주는 등록되지 않은 리스너를 통한 누수입니다. onStart에서 registerListener가 호출되었지만 onStop/onDestroy에서 unregisterListener가 호출되지 않은 경우, 액티비티가 파괴된 후에도 리스너 객체가 시스템에 의해 유지됩니다. LeakCanary는 어떤 리스너가 어떤 시스템 서비스에서 살아 있는지 명확하게 보여줍니다.

kotlin
// 일반적인 누수: 싱글톤 콜백에 캡처된 Activity
object AnalyticsManager {
    private var callback: ((String) -> Unit)? = null

    fun register(callback: (String) -> Unit) {
        this.callback = callback // 콜백에 대한 강한 참조
    }

    fun unregister() {
        callback = null // onDestroy에서 호출하는 것을 잊지 마세요!
    }
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        AnalyticsManager.register { event ->
            logEvent(event) // 람다가 this를 캡처합니다
        }
        // onDestroy에서 unregister를 호출하지 않으면 → Activity 누수
    }
}

세 번째 범주는 BackStack의 Fragment를 통한 누수입니다. FragmentTransaction.addToBackStack()이 뒤로 갈 때 Fragment를 제거하지 않고 호출되면, 이전 Fragment 인스턴스가 메모리에 남습니다. LeakCanary는 개발 초기 단계에서 이러한 숨은 누수를 감지하는 데 도움을 줍니다.

감지된 각 누수에 대해 LeakCanary는 설명과 수정 권장 사항을 제공합니다. 버전 2.14에서는 Android Lint와의 통합이 추가되어, CI에서 누수가 감지되면 라이브러리가 자동으로 이슈 트래커에 작업을 생성할 수 있습니다.

자주 묻는 질문

릴리스 APK에서 LeakCanary를 제거해야 하나요?

네, 반드시 그래야 합니다. LeakCanary는 build.gradle에서 debugImplementation을 통해 추가되므로 릴리스 빌드에서 자동으로 제외됩니다. implementation을 사용하면 라이브러리가 릴리스 APK에 포함되어 최종 사용자에게 누수를 표시하게 됩니다 — 이는 용납될 수 없습니다.

LeakCanary가 앱을 느리게 하나요?

성능에 미치는 영향은 최소화됩니다. LeakCanary는 구성 요소의 onDestroy 후에만 활성화되며 UI 렌더링이나 터치 처리에 간섭하지 않습니다. 유일한 비용은 강제 GC의 짧은 일시 중지(약 100ms)와 누수 발생 시 힙 덤프 쓰기(1초 미만)입니다.

LeakCanary 보고서를 내보내는 방법은?

LeakCanary는 자동으로 힙 덤프를 HPROF 형식으로 앱 폴더에 저장합니다. 파일은 Android Studio를 통해 내보낼 수 있습니다: Device File Explorer → data/data/com.example/files/leakcanary/. 보려면 Capture → Open Heap Dump를 통해 Memory Profiler에서 파일을 엽니다.

LeakCanary가 Jetpack Compose에서 작동하나요?

네, 버전 2.12부터 LeakCanary는 Jetpack Compose를 완전히 지원합니다. 라이브러리는 Composition 컨텍스트와 State 객체를 추적하고 Composable 함수의 누수를 자동으로 감지합니다. 별도의 설정이 필요 없습니다 — 바로 사용할 수 있습니다.

LeakCanary가 오탐지(false positive)를 일으킬 수 있나요?

오탐지 가능성은 있지만 드뭅니다. LeakCanary는 누수를 선언하기 전에 세 번의 GC 호출을 사용하므로 대부분의 오탐지를 제거합니다. 감지가 오탐지라고 생각되면 구성에서 특정 클래스에 대한 IgnoredReference를 생성하세요.

요약

  • LeakCanary는 Android 애플리케이션의 자동 메모리 누수 감지를 위한 표준 라이브러리입니다.
  • 라이브러리는 WeakReference와 강제 GC를 사용하여 라이프사이클을 초과하여 생존하는 객체를 감지합니다.
  • 힙 덤프는 내장 Shark 엔진에 의해 분석되며, GC Root에서 누수 객체까지의 참조 체인을 구축합니다.
  • 최신 프로젝트 설치: build.gradle에 한 줄: debugImplementation.
  • LeakCanary 2.x는 Kotlin으로 완전히 다시 작성되었으며 이전 버전보다 5~10배 빠릅니다.
  • 가장 흔한 누수: Activity에 대한 정적 참조, 등록되지 않은 리스너, BackStack의 Fragment.
  • 모든 프로젝트의 debug 빌드에 LeakCanary를 추가하세요 — 프로덕션에서 누수를 방지합니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기