본문 바로가기
■Development■/《Android》

[Android] GC_CONCURRENT FREED 라는 에러 메시지 완벽 이해하기

by 은스타 2020. 4. 8.
반응형

Android Studio에서 GC_CONCURRENT FREED 메시지 완벽 이해하기

안녕하세요.
이번 포스팅은 안드로이드 개발을 시작한 초보 개발자라면 Android Studio 로그창에서 GC_CONCURRENT freed 같은 메시지를 자주 보게 됩니다. 이 메시지가 정확히 무엇을 의미하는지, 에러인지 아닌지, 그리고 앱 성능에 어떤 영향을 미치는지 궁금하신가요? 이 글에서는 초보자도 쉽게 이해할 수 있도록 안드로이드의 가비지 컬렉션과 GC_CONCURRENT FREED 메시지에 대해 상세히 알아보겠습니다.


목차

 

#1. GC_CONCURRENT FREED는 에러가 아닙니다!

가장 먼저 알아두어야 할 점은 GC_CONCURRENT FREED는 에러가 아니라는 것입니다. 이것은 단순히 안드로이드 시스템에서 메모리 관리를 위해 가비지 컬렉션(Garbage Collection)이 실행되었다는 정보성 메시지입니다.

일반적인 GC_CONCURRENT FREED 메시지의 형태는 다음과 같습니다:

D/dalvikvm: GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms

 

#2. 가비지 컬렉션(Garbage Collection)이란?

가비지 컬렉션은 프로그램이 동적으로 할당했던 메모리 영역 중에서 더 이상 사용하지 않는 영역을 자동으로 탐지하고 해제하는 메모리 관리 기법입니다.

안드로이드 앱은 Java나 Kotlin으로 작성되며, 이러한 언어들은 개발자가 직접 메모리를 할당하고 해제하지 않습니다. 대신 가비지 컬렉터(Garbage Collector)가 주기적으로 실행되어 더 이상 사용되지 않는 객체들을 찾아 메모리에서 제거합니다.

가비지 컬렉션의 작동 방식

  1. 표시(Mark): 사용 중인 객체를 식별하고 표시합니다.
  2. 청소(Sweep): 표시되지 않은 객체(더 이상 참조되지 않는 객체)를 제거합니다.
  3. 압축(Compact): 남은 객체들을 메모리 공간에 재배치하여 메모리 단편화를 줄입니다.

 

#3. GC_CONCURRENT FREED 메시지 해석하기

GC_CONCURRENT FREED 메시지를 자세히 분석해보겠습니다:

D/dalvikvm: GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms

이 메시지가 가진 의미는 다음과 같습니다:

  • D/dalvikvm: Debug 로그 태그로, Dalvik 가상 머신(안드로이드 런타임 환경)에서 발생한 로그임을 나타냅니다.
  • GC_CONCURRENT: 가비지 컬렉션 유형을 나타냅니다. CONCURRENT는 앱 실행과 동시에 가비지 컬렉션이 수행되었음을 의미합니다.
  • freed 2049K: 가비지 컬렉션을 통해 확보(해제)된 메모리 양이 2049KB임을 나타냅니다.
  • 65% free 3571K/9991K: 힙(Heap) 메모리의 사용 상태를 보여줍니다. 전체 9991KB 중 3571KB가 사용 가능한 상태(free)이며, 이는 전체의 65%에 해당합니다.
  • external 4703K/5261K: 네이티브 힙에 할당된 메모리 상태를 나타냅니다. 전체 5261KB 중 4703KB가 사용 중입니다.
  • paused 2ms+2ms: 가비지 컬렉션으로 인해 앱 실행이 일시 중지된 시간을 나타냅니다. 첫 번째 2ms는 표시(Mark) 단계, 두 번째 2ms는 청소(Sweep) 단계에서 소요된 시간입니다.

 

#4. 안드로이드의 가비지 컬렉션 유형

안드로이드에서는 여러 유형의 가비지 컬렉션이 있으며, 로그 메시지로 확인할 수 있습니다:

가비지 컬렉션 유형 설명
GC_CONCURRENT 앱 실행과 동시에 발생하는 가비지 컬렉션으로, 앱의 실행을 최소한으로 방해합니다.
GC_FOR_MALLOC 메모리 할당 요청을 처리하기 위해 발생하는 가비지 컬렉션입니다.
GC_EXPLICIT System.gc()와 같은 명시적인 호출로 발생하는 가비지 컬렉션입니다.
GC_EXTERNAL_ALLOC 외부 메모리 할당을 위한 가비지 컬렉션입니다(안드로이드 2.3 이전 버전).
GC_HPROF_DUMP_HEAP 힙 덤프를 생성하기 위한 가비지 컬렉션입니다.

 

#5. 가비지 컬렉션이 자주 발생하는 경우

GC_CONCURRENT FREED 메시지 자체는 에러가 아니지만, 이러한 메시지가 매우 자주 발생한다면 메모리 사용에 문제가 있을 수 있습니다. 이는 다음과 같은 이유로 발생할 수 있습니다:

  1. 메모리 누수(Memory Leak): 더 이상 사용하지 않는 객체에 대한 참조가 유지되어 가비지 컬렉터가 해당 객체를 수거하지 못하는 상황
  2. 과도한 객체 생성: 짧은 시간에 많은 임시 객체를 생성하는 경우
  3. 큰 이미지나 데이터 처리: 대용량 이미지나 데이터를 효율적으로 처리하지 않는 경우

 

#6. 메모리 문제 디버깅 방법

안드로이드 앱의 메모리 사용을 확인하고 문제를 해결하기 위한 몇 가지 방법을 알아보겠습니다:

1. Android Studio Profiler 사용하기

Android Studio의 Profiler 도구는 앱의 메모리, CPU, 네트워크 사용량을 실시간으로 모니터링할 수 있게 해줍니다.

사용 방법:

  1. Android Studio에서 앱을 실행합니다.
  2. 하단의 'Profiler' 탭을 클릭합니다.
  3. 'MEMORY' 섹션을 선택하여 메모리 사용량을 모니터링합니다.

2. LeakCanary 라이브러리 사용하기

LeakCanary는 메모리 누수를 감지하고 알려주는 인기 있는 오픈 소스 라이브러리입니다.

앱의 build.gradle 파일에 다음과 같이 추가할 수 있습니다:

dependencies {
  // 디버그 빌드에만 LeakCanary 추가
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

3. 덤프 힙(Dump Heap) 분석하기

메모리 사용량이 비정상적으로 높을 때 힙 덤프를 생성하고 분석할 수 있습니다:

  1. Android Profiler에서 'Dump Java Heap' 버튼을 클릭합니다.
  2. 생성된 힙 덤프 파일(.hprof)을 분석하여 어떤 객체가 많은 메모리를 차지하는지 확인합니다.

 

#7. 메모리 사용 최적화 팁

안드로이드 앱의 메모리 사용을 최적화하기 위한 몇 가지 팁을 소개합니다:

1. 컨텍스트 참조 관리하기

Activity나 Fragment 같은 컨텍스트를 강한 참조(Strong Reference)로 유지하면 메모리 누수가 발생할 수 있습니다.

// 잘못된 예시
public class MyManager {
    private Context context;

    public MyManager(Context context) {
        this.context = context; // Activity 컨텍스트를 강한 참조로 유지
    }
}

// 개선된 예시
public class MyManager {
    private WeakReference<Context> contextRef;

    public MyManager(Context context) {
        this.contextRef = new WeakReference<>(context);
    }

    public void doSomething() {
        Context context = contextRef.get();
        if (context != null) {
            // 컨텍스트 사용
        }
    }
}

2. 이미지 효율적으로 로드하기

큰 이미지는 메모리를 많이 차지합니다. Glide나 Picasso 같은 이미지 로딩 라이브러리를 사용하면 이미지 로딩과 캐싱을 효율적으로 관리할 수 있습니다.

implementation 'com.github.bumptech.glide:glide:4.12.0'
// Glide를 사용한 이미지 로딩
Glide.with(context)
     .load(imageUrl)
     .override(300, 300) // 적절한 크기로 리사이징
     .centerCrop()
     .into(imageView);

3. 리스트 뷰 재활용 최적화하기

RecyclerView나 ListView에서 뷰 홀더 패턴을 제대로 구현하여 뷰 재활용을 최적화합니다.

// Kotlin으로 작성된 효율적인 RecyclerView.Adapter
class MyAdapter(private val items: List<Item>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // 새 뷰 생성은 필요할 때만
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // 기존 뷰 재활용
        holder.bind(items[position])
    }

    override fun getItemCount() = items.size

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: Item) {
            // 뷰 내용 업데이트
        }
    }
}

4. 메모리 캐시 관리하기

메모리 캐시는 성능을 향상시키지만, 크기를 적절히 관리해야 합니다.

// LruCache를 사용한 메모리 캐시 구현
int cacheSize = 4 * 1024 * 1024; // 4MB
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // 비트맵의 실제 바이트 크기 반환
        return bitmap.getByteCount();
    }
};

5. 불필요한 객체 생성 피하기

반복문 내에서 객체를 반복적으로 생성하는 것은 피해야 합니다.

// 잘못된 예시
for (int i = 0; i < 1000; i++) {
    String s = new String("객체 " + i); // 매 반복마다 새 객체 생성
    process(s);
}

// 개선된 예시
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    builder.setLength(0); // 기존 객체 재사용
    builder.append("객체 ").append(i);
    process(builder.toString());
}

 

#8. 가비지 컬렉션의 영향과 성능

가비지 컬렉션은 메모리 관리에 중요하지만, 앱 성능에 영향을 줄 수 있습니다:

  1. 앱 일시 중지: 가비지 컬렉션이 실행될 때 앱이 잠시 멈출 수 있습니다. 특히 메모리 사용량이 많은 경우 이러한 일시 중지가 눈에 띄게 됩니다.
  2. 배터리 소모: 잦은 가비지 컬렉션은 CPU 사용량을 증가시켜 배터리 소모를 높입니다.
  3. UI 지연: 메인 스레드에서 가비지 컬렉션이 발생하면 UI 렌더링이 지연될 수 있습니다.

 

#9. 결론: GC_CONCURRENT FREED 메시지는 친구입니다

GC_CONCURRENT FREED 메시지는 에러가 아니라 안드로이드 시스템이 정상적으로 메모리를 관리하고 있다는 신호입니다. 이 메시지 자체는 걱정할 필요가 없지만, 다음과 같은 경우에는 주의가 필요합니다:

  • 매우 짧은 간격으로 계속해서 발생하는 경우
  • 'paused' 시간이 길어지는 경우 (10ms 이상)
  • 앱이 느려지거나 프레임이 떨어지는 현상과 함께 발생하는 경우

이러한 상황에서는 앞서 설명한 도구와 방법을 활용하여 앱의 메모리 사용을 최적화하는 것이 좋습니다.

메모리 관리는 안드로이드 앱 개발에서 중요한 부분이지만, 초보 개발자라면 처음부터 모든 것을 완벽하게 할 필요는 없습니다. 기본적인 원칙을 이해하고, 앱을 개발하면서 점진적으로 최적화해 나가는 것이 중요합니다.

이제 Android Studio 로그창에서 GC_CONCURRENT FREED 메시지를 봐도 당황하지 않고, 앱의 메모리 건강 상태를 확인하는 지표로 활용할 수 있을 것입니다! 

 

#10. 자주 묻는 질문

Q: GC_CONCURRENT FREED 메시지를 로그에서 숨길 수 있나요?

A: 네, 로그캣(Logcat)에서 필터를 설정하여 특정 태그나 메시지를 제외할 수 있습니다. "dalvikvm" 태그를 필터링하거나, "GC_"로 시작하는 메시지를 제외하도록 설정할 수 있습니다.

Q: System.gc()를 호출해도 괜찮을까요?

A: 일반적으로 System.gc()를 명시적으로 호출하는 것은 권장되지 않습니다. 자바와 안드로이드 런타임은 가비지 컬렉션을 효율적으로 관리하도록 설계되어 있으며, 명시적인 호출은 시스템의 최적화를 방해할 수 있습니다.

Q: 앱이 Out of Memory 에러를 발생시키는데, 가비지 컬렉션과 관련이 있나요?

A: Out of Memory(OOM) 에러는 가비지 컬렉션이 충분한 메모리를 확보하지 못할 때 발생할 수 있습니다. 이는 메모리 누수가 있거나, 앱이 사용 가능한 메모리보다 더 많은 메모리를 요구할 때 나타납니다. 메모리 프로파일링을 통해 원인을 파악하고 해결해야 합니다.

 

긴 글 읽어주셔서 감사합니다.

끝. 


#11. 추가 자료

안드로이드 메모리 관리와 가비지 컬렉션에 대해 더 자세히 알아보려면 다음 자료를 참고하세요:

반응형