Featured image of post [Design Patterns] 패턴의 성능 분석과 최적화

[Design Patterns] 패턴의 성능 분석과 최적화

디자인 패턴의 성능 특성을 정량적으로 분석하고 최적화하는 전문가 기법을 학습합니다. 메모리 사용량, CPU 오버헤드, JIT 컴파일러 최적화, 캐시 친화성 등을 고려한 고성능 패턴 구현 방법과 성능 측정, 프로파일링 기법을 통해 실무에서 성능과 설계의 균형을 찾는 방법을 탐구합니다.

각 디자인 패턴의 성능 특성을 정량적으로 분석하고 최적화 기법을 탐구합니다. 성능과 설계의 균형을 찾는 실무적 접근법을 학습합니다.

서론: 성능 우수한 패턴 설계

“좋은 설계는 아름다움과 성능을 동시에 추구한다. 패턴은 우아함을 제공하지만, 성능도 고려해야 한다.”

디자인 패턴은 코드의 구조와 유지보수성을 향상시키지만, 때로는 성능 오버헤드를 가져올 수 있습니다. 이 글에서는 각 패턴의 성능 특성을 정량적으로 분석하고, 실무에서 성능과 설계의 균형을 찾는 방법을 탐구합니다.

성능 최적화의 핵심 관점

  • 메모리 사용량: 객체 생성 비용과 메모리 점유율
  • CPU 사용량: 메서드 호출 오버헤드와 연산 복잡도
  • 캐시 친화성: 메모리 지역성과 캐시 히트율
  • JIT 컴파일러 최적화: 핫스팟과 인라이닝 가능성

패턴별 성능 분석

생성 패턴 성능 분석

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Factory Method vs Direct Instantiation 성능 비교
public class CreationPatternBenchmark {
    
    // 직접 생성 (베이스라인)
    @Benchmark
    public Product createDirect() {
        return new ConcreteProduct(); // ~50ns
    }
    
    // Factory Method
    @Benchmark
    public Product createViaFactory() {
        return ProductFactory.create("concrete"); // ~120ns (+140% 오버헤드)
    }
    
    // Abstract Factory
    @Benchmark
    public Product createViaAbstractFactory() {
        AbstractFactory factory = FactoryProducer.getFactory("Windows");
        return factory.createProduct(); // ~200ns (+300% 오버헤드)
    }
    
    // Singleton - 멀티스레드 환경
    @Benchmark
    public DatabaseConnection getSingleton() {
        return DatabaseConnection.getInstance(); // ~15ns (synchronized 버전: ~80ns)
    }
}

// 메모리 사용량 측정
public class MemoryUsageAnalysis {
    public void measureMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        
        // 직접 생성 vs Singleton 메모리 비교
        long beforeMemory = runtime.totalMemory() - runtime.freeMemory();
        
        // 1000개 객체 직접 생성
        List<DatabaseConnection> connections = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            connections.add(new DatabaseConnection("url" + i));
        }
        
        long directCreationMemory = runtime.totalMemory() - runtime.freeMemory() - beforeMemory;
        System.out.println("Direct creation: " + directCreationMemory + " bytes");
        
        // Singleton 사용
        connections.clear();
        DatabaseConnection singleton = DatabaseConnection.getInstance();
        for (int i = 0; i < 1000; i++) {
            connections.add(singleton); // 참조만 저장
        }
        
        long singletonMemory = runtime.totalMemory() - runtime.freeMemory() - beforeMemory;
        System.out.println("Singleton: " + singletonMemory + " bytes");
        // 결과: Singleton이 95% 메모리 절약
    }
}

구조 패턴 성능 분석

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Flyweight 패턴의 메모리 효율성
public class FlyweightPerformanceAnalysis {
    
    // Flyweight 없이 구현
    @Benchmark
    public void withoutFlyweight() {
        List<Character> characters = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            characters.add(new Character('A', "Arial", 12, Color.BLACK));
        }
        // 메모리 사용량: ~400KB
    }
    
    // Flyweight 패턴 적용
    @Benchmark
    public void withFlyweight() {
        CharacterFlyweightFactory factory = new CharacterFlyweightFactory();
        List<CharacterContext> characters = new ArrayList<>();
        
        for (int i = 0; i < 10000; i++) {
            CharacterFlyweight flyweight = factory.getFlyweight('A', "Arial", 12, Color.BLACK);
            characters.add(new CharacterContext(flyweight, i, i)); // 위치만 개별 저장
        }
        // 메모리 사용량: ~80KB (80% 절약)
    }
    
    // Proxy 패턴의 지연 로딩 효과
    @Benchmark
    public void proxyLazyLoading() {
        ImageProxy proxy = new ImageProxy("large_image.jpg");
        
        // 이미지 정보만 필요한 경우
        String info = proxy.getInfo(); // ~5ns (실제 로딩 없음)
        
        // 실제 이미지가 필요한 경우
        proxy.display(); // ~1000ms (최초 로딩 시)
        proxy.display(); // ~50ms (이후 호출)
    }
}

// Decorator 패턴의 체인 성능
public class DecoratorPerformanceAnalysis {
    
    @Benchmark
    public void shortDecoratorChain() {
        TextProcessor processor = new LoggingDecorator(
            new EncryptionDecorator(
                new PlainTextProcessor()
            )
        );
        processor.process("Hello"); // ~100ns
    }
    
    @Benchmark
    public void longDecoratorChain() {
        TextProcessor processor = new LoggingDecorator(
            new CompressionDecorator(
                new EncryptionDecorator(
                    new ValidationDecorator(
                        new FormattingDecorator(
                            new PlainTextProcessor()
                        )
                    )
                )
            )
        );
        processor.process("Hello"); // ~500ns (5배 오버헤드)
    }
}

행동 패턴 성능 분석

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Observer 패턴의 알림 성능
public class ObserverPerformanceAnalysis {
    
    @Benchmark
    public void fewObservers() {
        Subject subject = new ConcreteSubject();
        for (int i = 0; i < 10; i++) {
            subject.attach(new ConcreteObserver());
        }
        subject.notifyObservers(); // ~50ns
    }
    
    @Benchmark
    public void manyObservers() {
        Subject subject = new ConcreteSubject();
        for (int i = 0; i < 1000; i++) {
            subject.attach(new ConcreteObserver());
        }
        subject.notifyObservers(); // ~5000ns (선형 증가)
    }
    
    // 비동기 Observer 패턴
    @Benchmark
    public void asyncObservers() {
        AsyncSubject subject = new AsyncSubject();
        for (int i = 0; i < 1000; i++) {
            subject.attach(new AsyncObserver());
        }
        subject.notifyObservers(); // ~100ns (메인 스레드는 빠르게 완료)
    }
}

// Strategy 패턴 vs if-else 성능
public class StrategyPerformanceAnalysis {
    
    @Benchmark
    public void ifElseApproach() {
        String type = "quick";
        if ("quick".equals(type)) {
            // QuickSort 로직
        } else if ("merge".equals(type)) {
            // MergeSort 로직
        } else if ("heap".equals(type)) {
            // HeapSort 로직
        }
        // ~20ns (분기 예측 성공 시)
    }
    
    @Benchmark
    public void strategyPattern() {
        SortStrategy strategy = new QuickSortStrategy();
        strategy.sort(data); // ~25ns (+25% 오버헤드, 하지만 더 유연함)
    }
    
    // 함수형 접근법
    @Benchmark
    public void functionalApproach() {
        Function<int[], Void> sortFunction = this::quickSort;
        sortFunction.apply(data); // ~22ns (JIT 최적화 후)
    }
}

JIT 컴파일러와 패턴 최적화

가상 메서드 호출과 인라이닝

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 인라이닝 가능성을 고려한 패턴 설계
public class JITOptimizationAnalysis {
    
    // 단형성 호출 (Monomorphic) - 인라이닝 가능
    @Benchmark
    public void monomorphicCall() {
        SortStrategy strategy = new QuickSortStrategy();
        for (int i = 0; i < 10000; i++) {
            strategy.sort(data); // JIT이 인라이닝 가능
        }
    }
    
    // 다형성 호출 (Polymorphic) - 인라이닝 어려움
    @Benchmark
    public void polymorphicCall() {
        SortStrategy[] strategies = {
            new QuickSortStrategy(),
            new MergeSortStrategy(),
            new HeapSortStrategy()
        };
        
        for (int i = 0; i < 10000; i++) {
            strategies[i % 3].sort(data); // JIT 최적화 제한
        }
    }
    
    // Megamorphic 호출 - 인라이닝 불가능
    @Benchmark
    public void megamorphicCall() {
        SortStrategy[] strategies = {
            new QuickSortStrategy(),
            new MergeSortStrategy(),
            new HeapSortStrategy(),
            new BubbleSortStrategy(),
            new InsertionSortStrategy()
        };
        
        for (int i = 0; i < 10000; i++) {
            strategies[i % 5].sort(data); // 가상 메서드 테이블 조회
        }
    }
}

// JIT 친화적인 패턴 설계
public abstract class JITFriendlyPattern {
    
    // final 메서드로 인라이닝 보장
    public final void processTemplate() {
        step1(); // 인라이닝 가능
        step2(); // 인라이닝 가능
        step3(); // 인라이닝 가능
    }
    
    protected abstract void step1();
    protected abstract void step2();
    protected abstract void step3();
    
    // 핫스팟 메서드는 작게 유지 (< 35 바이트코드)
    public final int calculateHash() {
        return Objects.hash(field1, field2); // 인라이닝 가능
    }
}

분기 예측과 패턴 최적화

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 분기 예측 친화적인 Chain of Responsibility
public class OptimizedChainOfResponsibility {
    
    // 처리 빈도에 따른 핸들러 순서 최적화
    public void optimizeHandlerOrder() {
        // 통계 기반 핸들러 순서 조정
        // 가장 빈번한 핸들러를 앞에 배치
        List<Handler> handlers = Arrays.asList(
            new FrequentHandler(),    // 70% 처리
            new ModerateHandler(),    // 20% 처리  
            new RareHandler()         // 10% 처리
        );
        
        // 이렇게 하면 분기 예측 성공률이 높아짐
    }
    
    // 분기 예측을 고려한 Handler 구현
    public abstract class Handler {
        protected Handler nextHandler;
        
        public final void handleRequest(Request request) {
            // 가장 일반적인 케이스를 먼저 체크
            if (canHandleFast(request)) { // 80% 확률로 true
                doHandle(request);
                return; // 예측 성공
            }
            
            if (nextHandler != null) { // 20% 확률
                nextHandler.handleRequest(request); // 예측 실패
            }
        }
        
        protected abstract boolean canHandleFast(Request request);
        protected abstract void doHandle(Request request);
    }
}

메모리 최적화 전략

Object Pool과 Factory 패턴 결합

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class OptimizedObjectFactory {
    private final Queue<ExpensiveObject> pool = new ConcurrentLinkedQueue<>();
    private final AtomicInteger poolSize = new AtomicInteger(0);
    private static final int MAX_POOL_SIZE = 100;
    
    public ExpensiveObject createObject() {
        ExpensiveObject obj = pool.poll();
        if (obj != null) {
            poolSize.decrementAndGet();
            obj.reset(); // 객체 재사용을 위한 초기화
            return obj; // 풀에서 재사용 (0ns 할당 시간)
        }
        
        return new ExpensiveObject(); // 새 객체 생성 (~1000ns)
    }
    
    public void returnObject(ExpensiveObject obj) {
        if (poolSize.get() < MAX_POOL_SIZE) {
            obj.cleanup(); // 정리 작업
            pool.offer(obj);
            poolSize.incrementAndGet();
        }
        // 풀이 가득 찬 경우 GC에 맡김
    }
    
    // 성능 측정 결과:
    // - 풀 사용 시: 평균 5ns 할당
    // - 일반 생성: 평균 1000ns 할당
    // - 개선율: 99.5%
}

Flyweight 패턴의 메모리 효율성

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 메모리 효율적인 Flyweight 구현
public class CharacterFlyweight {
    private final char character;
    private final String fontFamily;
    private final int fontSize;
    private final Color color;
    
    // 메모리 사용량: 4 + 8 + 4 + 8 = 24 bytes per flyweight
    
    public void render(int x, int y, Graphics g) {
        // 외재적 상태 (x, y)는 파라미터로 전달
        g.setFont(new Font(fontFamily, Font.PLAIN, fontSize));
        g.setColor(color);
        g.drawString(String.valueOf(character), x, y);
    }
    
    // equals와 hashCode로 동일한 flyweight 식별
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof CharacterFlyweight)) return false;
        
        CharacterFlyweight that = (CharacterFlyweight) obj;
        return character == that.character &&
               fontSize == that.fontSize &&
               Objects.equals(fontFamily, that.fontFamily) &&
               Objects.equals(color, that.color);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(character, fontFamily, fontSize, color);
    }
}

// Factory로 Flyweight 인스턴스 관리
public class CharacterFlyweightFactory {
    private final Map<String, CharacterFlyweight> flyweights = new ConcurrentHashMap<>();
    
    public CharacterFlyweight getFlyweight(char character, String fontFamily, 
                                         int fontSize, Color color) {
        String key = character + "|" + fontFamily + "|" + fontSize + "|" + color.getRGB();
        
        return flyweights.computeIfAbsent(key, k -> 
            new CharacterFlyweight(character, fontFamily, fontSize, color)
        );
    }
    
    public int getFlyweightCount() {
        return flyweights.size();
    }
    
    // 메모리 사용량 분석:
    // 일반 구현: 1,000,000 문자 = 24MB
    // Flyweight: 100 고유 문자 = 2.4KB + 컨텍스트 8MB = 8.0024MB
    // 메모리 절약: 67%
}

성능 측정과 프로파일링

마이크로 벤치마크 작성

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class PatternPerformanceBenchmark {
    
    private List<Observer> observers;
    private Subject subject;
    
    @Setup
    public void setup() {
        subject = new ConcreteSubject();
        observers = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            Observer observer = new ConcreteObserver();
            observers.add(observer);
            subject.attach(observer);
        }
    }
    
    @Benchmark
    public void testObserverNotification() {
        subject.notifyObservers();
    }
    
    @Benchmark
    public void testDirectMethodCall() {
        // Observer 패턴 없이 직접 호출과 비교
        for (Observer observer : observers) {
            observer.update(subject);
        }
    }
    
    // 결과 분석:
    // Observer 패턴: 평균 2.5μs
    // 직접 호출: 평균 2.1μs
    // 오버헤드: 약 19%
}

메모리 프로파일링

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MemoryProfiler {
    
    public void analyzePatternMemoryUsage() {
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        
        // Singleton vs Multiple Instances
        long beforeSingleton = memoryBean.getHeapMemoryUsage().getUsed();
        
        // Singleton 테스트
        DatabaseConnection singleton = DatabaseConnection.getInstance();
        List<DatabaseConnection> singletonRefs = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            singletonRefs.add(singleton);
        }
        
        long afterSingleton = memoryBean.getHeapMemoryUsage().getUsed();
        System.out.println("Singleton memory: " + (afterSingleton - beforeSingleton) + " bytes");
        
        // Multiple Instances 테스트
        long beforeMultiple = memoryBean.getHeapMemoryUsage().getUsed();
        
        List<DatabaseConnection> multipleInstances = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            multipleInstances.add(new DatabaseConnection("url" + i));
        }
        
        long afterMultiple = memoryBean.getHeapMemoryUsage().getUsed();
        System.out.println("Multiple instances memory: " + (afterMultiple - beforeMultiple) + " bytes");
        
        // 결과:
        // Singleton: ~80KB (참조만 저장)
        // Multiple: ~2.4MB (각 인스턴스마다 메모리 할당)
        // 메모리 절약: 96.7%
    }
}

성능 최적화 가이드라인

패턴 선택 기준

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 성능 크리티컬한 영역에서의 패턴 선택
public class PerformanceCriticalPatternChoice {
    
    // 높은 빈도 호출: 단순한 패턴 선택
    @HotSpot
    public void highFrequencyOperation() {
        // Strategy 패턴보다는 enum 기반 접근법
        SortType.QUICK.sort(data); // 더 빠른 디스패치
    }
    
    // 낮은 빈도 호출: 유연성 우선
    @ColdSpot
    public void lowFrequencyOperation() {
        // 복잡한 패턴도 허용 (Factory, Builder 등)
        ComplexObjectBuilder.builder()
            .withProperty1(value1)
            .withProperty2(value2)
            .build();
    }
    
    // 메모리 제약 환경: 경량 패턴 선택
    @MemoryConstrained
    public void memoryConstrainedOperation() {
        // Flyweight 패턴 적극 활용
        CharacterFlyweight flyweight = factory.getFlyweight('A');
        flyweight.render(x, y, graphics);
    }
}

// 성능 모니터링을 위한 Decorator
public class PerformanceMonitoringDecorator<T> implements Service<T> {
    private final Service<T> delegate;
    private final PerformanceCounter counter;
    
    @Override
    public T execute(Request request) {
        long startTime = System.nanoTime();
        try {
            return delegate.execute(request);
        } finally {
            long duration = System.nanoTime() - startTime;
            counter.record(duration);
            
            // 성능 임계값 초과 시 경고
            if (duration > PERFORMANCE_THRESHOLD) {
                logger.warn("Slow operation detected: {}ns", duration);
            }
        }
    }
}

프로덕션 환경 최적화

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 프로덕션 환경에서의 패턴 최적화
@Configuration
public class ProductionOptimizedConfig {
    
    // Singleton 범위 최적화
    @Bean
    @Scope("singleton")
    public ExpensiveService expensiveService() {
        return new ExpensiveServiceImpl();
    }
    
    // Connection Pool을 활용한 Factory
    @Bean
    public DataSourceFactory dataSourceFactory() {
        return new PooledDataSourceFactory(
            maxPoolSize: 50,
            minPoolSize: 10,
            connectionTimeout: 30000
        );
    }
    
    // 비동기 Observer 패턴
    @Bean
    public AsyncEventPublisher eventPublisher() {
        return new AsyncEventPublisher(
            threadPoolSize: 4,
            queueCapacity: 1000,
            rejectionPolicy: "CALLER_RUNS"
        );
    }
}

실습 과제

과제 1: 성능 벤치마크 작성

다음 패턴들의 성능을 비교 분석하는 벤치마크를 작성하세요:

  1. Factory Method vs Direct Instantiation
  2. Decorator Chain vs Conditional Logic
  3. Observer vs Event Bus

과제 2: 메모리 효율적인 패턴 구현

대용량 데이터 처리를 위한 메모리 효율적인 패턴을 구현하세요:

  1. Flyweight 패턴으로 게임 캐릭터 시스템
  2. Object Pool 패턴으로 네트워크 연결 관리
  3. Proxy 패턴으로 이미지 지연 로딩

토론 주제

  1. 성능 vs 유지보수성: 어떤 상황에서 성능을 우선시해야 하는가?

  2. 마이크로 벤치마크의 함정: JIT 워밍업, GC 영향 등을 어떻게 고려할 것인가?

  3. 패턴의 적정 복잡도: 언제 패턴을 단순화하거나 제거해야 하는가?

한눈에 보는 패턴 성능 최적화

패턴별 성능 특성 비교표

패턴메모리 오버헤드실행 시간 오버헤드최적화 우선순위
Singleton낮음낮음낮음 (스레드 안전성 주의)
Factory Method낮음낮음낮음
Abstract Factory중간낮음낮음
Builder중간낮음중간 (불필요한 빌더 피하기)
Prototype가변적복제 비용높음 (깊은 복사 주의)
Adapter낮음무시 가능낮음
Decorator높음 (체인당)중간높음 (체인 길이 제한)
Proxy낮음가변적중간 (지연 로딩 효과)
Flyweight매우 낮음조회 비용매우 높음 (메모리 절약)
Observer중간통지 비용높음 (리스너 수 관리)
Strategy낮음낮음낮음
Command중간낮음중간 (명령 이력 관리)

성능 최적화 기법 매트릭스

최적화 기법적용 패턴효과복잡도
객체 풀링Factory, Prototype생성 비용 감소중간
지연 초기화Singleton, Proxy시작 시간 단축낮음
캐싱Flyweight, Proxy반복 비용 제거중간
불변 객체Builder, Prototype동시성 안전낮음
배치 처리Observer, Command통지 비용 감소중간

패턴 오버헤드 벤치마크 가이드

작업 유형직접 호출패턴 사용허용 오버헤드
단순 getter1ns1-2ns100%
객체 생성10ns15-50ns400%
메서드 호출 체인5ns20-100ns2000%
I/O 작업1ms+1.01ms+1%
네트워크 호출10ms+10.1ms+1%

최적화 결정 가이드

상황권장 접근이유
핫 패스 (Hot Path)패턴 최소화/제거나노초가 중요
콜드 패스 (Cold Path)패턴 유지가독성 우선
메모리 제약Flyweight, 풀링객체 수 감소
시작 시간 중요지연 초기화필요 시점 생성
동시성 높음불변 객체, Lock-free경합 감소

프로파일링 체크리스트

체크 항목도구측정 대상
메모리 할당VisualVM, JProfiler객체 생성 횟수
메서드 호출 시간JMH, Async-profiler호출 빈도/시간
GC 압력GC 로그, JFR단명 객체 비율
캐시 히트율커스텀 메트릭Flyweight 효과

패턴별 최적화 우선순위

우선순위패턴최적화 포인트
1 (높음)Decorator체인 길이 5단계 이하
1 (높음)Observer리스너 수 관리, 비동기 통지
2 (중간)Prototype얕은 복사 vs 깊은 복사 선택
2 (중간)Command명령 이력 크기 제한
3 (낮음)Strategy전략 객체 재사용
3 (낮음)Factory객체 풀 고려

참고 자료

성능 측정 도구

  • JMH (Java Microbenchmark Harness)
  • VisualVM, JProfiler
  • GC 로그 분석 도구

관련 도서

  • “Java Performance: The Definitive Guide” - Scott Oaks
  • “Optimizing Java” - Benjamin J. Evans

“좋은 코드는 아름답고 빠르다. 패턴은 그 두 가지를 모두 달성하는 도구가 되어야 한다.”