WeakHashMap을 활용한 자동 메모리 정리 캐시 구현

WeakHashMap를 이용한 캐시 자동 정리 기법

자바에서 메모리 관리는 특히 캐시 시스템 구현 시 핵심적인 고려 사항입니다. 캐시 항목이 더 이상 사용되지 않으면 자동으로 제거되어야 하며, 수동 삭제 없이도 이를 보장하는 방식이 필요합니다. WeakHashMap은 이러한 요구를 충족시키기 위해 제공되는 유용한 도구로, 키에 약한 참조(weak reference)를 사용하여 메모리 누수를 줄이고 자동 정리를 가능하게 합니다.

본 문서에서는 WeakHashMap을 활용해 캐시 항목이 사용되지 않으면 자동으로 제거되는 시스템을 어떻게 설계할 수 있는지 실습 중심으로 설명합니다.

WeakHashMap의 개념

WeakHashMap은 자바의 Map 인터페이스를 구현한 컬렉션으로, 저장된 키는 약한 참조로 관리됩니다. 즉, 해당 키 객체가 더 이상 강한 참조(강한 참조)에 의해 참조되지 않으면, 가비지 컬렉터가 그 객체를 회수하고, 이에 따라 WeakHashMap 내부에서도 해당 엔트리가 자동으로 제거됩니다.

주요 특징

  • 키에 약한 참조 사용: 키는 강한 참조가 아닌 약한 참조로 저장되며, 외부에서 더 이상 참조하지 않으면 자동으로 해제됨.
  • 자동 정리 기능: 참조가 사라진 키는 자동으로 삭제되어 메모리 누수 위험 감소.
  • 캐시 시나리오 최적화: 불필요한 캐시 항목이 자동으로 정리되므로, 장기적으로 메모리 효율성 향상.

실용 예제: 캐시 자동 정리 시스템 구현

다음은 데이터베이스 쿼리 결과를 캐시하는 시스템을 구현한 예제입니다. 쿼리 조건이 더 이상 필요하지 않으면, 그에 해당하는 캐시 항목이 자동으로 삭제됩니다.

import java.util.Map;
import java.util.WeakHashMap;

public class QueryCache {
    // WeakHashMap으로 캐시 저장 (키: 쿼리문, 값: 결과)
    private final Map<String, String> cache = new WeakHashMap<>();

    // 실제 데이터베이스 조회를 시뮬레이션
    public String fetchResult(String query) {
        // 캐시에 존재 여부 확인
        String cached = cache.get(query);
        if (cached != null) {
            System.out.println("캐시 히트: " + query);
            return cached;
        }

        // 캐시 미스 → 실제 데이터베이스 요청 시뮬레이션
        System.out.println("데이터베이스 조회 중: " + query);
        String result = "결과: " + query;

        // 결과 캐시에 저장
        cache.put(query, result);

        return result;
    }

    public static void main(String[] args) {
        QueryCache cacheManager = new QueryCache();

        // 두 개의 쿼리 생성
        String q1 = "SELECT * FROM users";
        String q2 = "SELECT * FROM orders";

        // 첫 번째 조회 (캐시 미스)
        System.out.println(cacheManager.fetchResult(q1));
        System.out.println(cacheManager.fetchResult(q2));

        // 두 번째 조회 (캐시 히트)
        System.out.println(cacheManager.fetchResult(q1));

        // q1에 대한 강한 참조 제거 → 더 이상 참조되지 않음
        q1 = null;

        // 명시적 가비지 컬렉션 실행
        System.gc();

        try {
            Thread.sleep(500); // GC가 완료될 시간 확보
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 캐시 상태 출력 (정리된 항목 확인)
        System.out.println("GC 이후 캐시 상태: " + cacheManager.cache);
    }
}
코드 분석
  1. 캐시 구성: WeakHashMap<String, String>을 사용하여 쿼리 문자열을 키로, 결과 문자열을 값으로 저장.
  2. 조회 로직: 캐시에 이미 존재하면 바로 반환; 없으면 시뮬레이션된 데이터베이스 조회 후 캐시에 저장.
  3. 정리 트리거: q1 변수를 null로 설정하면, 해당 키에 대한 강한 참조가 사라지고, 다음 가비지 컬렉션에서 자동으로 제거됨.
  4. 가비지 컬렉션 강제 실행: System.gc() 호출로 메모리 정리 프로세스를 유도하며, Thread.sleep()로 정리 완료를 대기.
실행 결과
데이터베이스 조회 중: SELECT * FROM users
데이터베이스 조회 중: SELECT * FROM orders
캐시 히트: SELECT * FROM users
GC 이후 캐시 상태: {SELECT * FROM orders=결과: SELECT * FROM orders}

관찰 가능한 현상

  • 처음 두 번의 조회는 캐시 미스이며, 실제 데이터베이스 조회 수행.
  • 두 번째 조회에서 캐시 히트 발생 → 성능 향상.
  • q1null로 설정하고 가비지 컬렉션을 실행한 후, q1에 해당하는 캐시 항목이 사라짐.
  • q2는 여전히 참조되고 있으므로, 캐시에 유지됨.
적합한 사용 시나리오
  • 임시 캐시 저장소: 자주 사용되지 않는 데이터를 일시적으로 캐시할 때, 사용되지 않으면 자동 정리됨.
  • 객체 풀 관리: 재사용 가능한 객체들을 관리할 때, 더 이상 사용되지 않은 객체를 자동으로 제거.
  • GUI 컴포넌트 캐싱: UI 요소가 소멸되면 관련 캐시도 함께 정리되어 메모리 누수 방지.
결론

WeakHashMap은 자바에서 메모리 리소스를 효율적으로 관리할 수 있도록 도와주는 강력한 도구입니다. 약한 참조를 통해 캐시 항목의 생명 주기를 자동으로 관리함으로써, 수동 정리의 부담을 줄이고 메모리 누수를 예방할 수 있습니다. 본 예제를 통해 캐시 시스템을 구현하면서 자동 정리 기능을 효과적으로 적용하는 방법을 이해할 수 있었습니다.

이 기술은 캐시, 오브젝트 풀, 렌더링 캐시 등 다양한 환경에서 활용 가능하며, 지속적인 메모리 효율성을 유지하는 데 큰 도움이 됩니다.

태그: java WeakHashMap 캐시 메모리 관리 가비지 컬렉션

6월 2일 21:24에 게시됨