스택큐힙리스트

Flyweight 패턴: 객체 수백만 개도 ‘가벼운’ 메모리로 돌리는 비결 본문

개발

Flyweight 패턴: 객체 수백만 개도 ‘가벼운’ 메모리로 돌리는 비결

스택큐힙리스트 2025. 7. 19. 12:39
반응형

1️⃣ Flyweight가 뭐길래?

‘Flyweight’는 권투 체급 중 가장 가벼운 무게를 뜻한다. 이름 그대로, 비슷한 객체들을 공유해 애플리케이션의 메모리 부담을 극적으로 줄이는 구조(Structural) 패턴이다. 핵심 아이디어는 변하지 않는 내부 상태(intrinsic)만 재사용하고, 요청마다 달라지는 외부 상태(extrinsic)는 호출 시점에 주입하는 것!


2️⃣ 언제 써야 효과가 클까?

  • 그래픽·게임 오브젝트 : 수천 ~ 수백만 개의 파티클·타일·총알·나무 등을 찍어낼 때
  • 텍스트 렌더링 : 글리프(폰트 문자) 객체를 문자마다 새로 만들면 메모리 폭발 → Flyweight로 공유
  • UI 테마·아이콘 : 동일 SVG/Icon을 컴포넌트마다 복사하는 대신 캐싱
  • 캐시 가능한 DB 룩업 값 : 코드표, 국가·통화 목록처럼 변하지 않는 데이터

3️⃣ 코드 스케치 – Java 21 예시

// ① Flyweight(공유 객체) – TreeType
public record TreeType(String name, Color color, String texture) { }

// ② Flyweight Factory – 캐시 & 재사용
public class TreeFactory {
    private static final Map<String, TreeType> CACHE = new ConcurrentHashMap<>();
    public static TreeType get(String name, Color color, String texture) {
        return CACHE.computeIfAbsent(
            name, k -> new TreeType(name, color, texture)
        );
    }
}

// ③ Context 객체 – 외부 상태(좌표)만 보유
public record Tree(int x, int y, TreeType type) {
    public void draw(Graphics g) {
        // 외부 상태 x, y + 공유된 type 정보로 그리기
    }
}

// 사용
TreeType oak = TreeFactory.get("oak", BROWN, "/oak.png");
List<Tree> forest = IntStream.range(0, 1_000_000)
    .mapToObj(i -> new Tree(randX(), randY(), oak))
    .toList();

동일 TreeType이 단 하나만 생성되므로, 1 백만 그루를 그려도 실 메모리는 극소량!


4️⃣ 구현 팁 🔥

  1. Immutable 객체로 만들기: 공유하려면 변경 불가능해야 안전하다. record·val·final 적극 사용.
  2. 팩토리로만 생성: new를 막고 캐시된 인스턴스를 돌려줘야 진짜 Flyweight.
  3. 메모리 vs CPU 트레이드오프: 상태 분리를 지나치면 오히려 계산·조회 비용이 커질 수 있으니 프로파일링 필수.
  4. GC 친화도 체크: 대량 Context 객체가 짧은 생명주기를 갖는다면, GC 튜닝(Region Size, Escape Analysis)을 병행.

5️⃣ 장점 vs. 주의점

  • 👍 메모리 사용량 감소: 비슷한 객체 공유로 Heap 절약.
  • 👍 성능 향상: 객체 생성·GC 비용이 급감.
  • ⚠️ 코드 복잡도: intrinsic-extrinsic 분리가 어려우면 가독성이 떨어진다.
  • ⚠️ 스레드 안정성: 공유 객체를 수정하면 전역 부작용 발생 → 불변성 유지가 필수.

한 줄 요약

Flyweight 패턴은 “속성은 공유하고, 좌표만 달리해” 수백만 객체도 가볍게 돌리는 메모리 마법이다.

반응형
Comments