자바 성능 최적화: 코드를 느리게 만드는 8가지 안티패턴

안티 패턴 수정으로 인한 성능 개선
Jonathan Vogel이 구축한 Java 주문 처리 애플리케이션은 초기 경과 시간 1,198ms, 초당 85,000건 처리, 1GB 이상 힙 사용, 19회 GC 일시 정지 성능을 보였습니다. 아키텍처 변경이나 JDK 업데이트 없이 8가지 안티 패턴을 수정한 후, 성능이 경과 시간 239ms, 초당 419,000건 처리, 139MB 힙 사용, 4회 GC 일시 정지로 개선되었습니다. 이는 처리량 5배 증가, 힙 사용량 87% 감소, GC 일시 정지 79% 감소를 의미합니다.
수정해야 할 8가지 Java 성능 안티 패턴
- 루프 내 문자열 연결 - 불변성으로 인한 O(n²) 복사
- 루프 내 O(n²) 스트림 반복 - 요소마다 전체 목록 스트리밍
- 핫 경로에서의 String.format() 사용 - 가장 느린 문자열 빌더, 매 호출마다 형식 파싱
- 핫 경로에서의 오토박싱 - 수백만 개의 일회용 래퍼 객체 생성
- 제어 흐름을 위한 예외 사용 - fillInStackTrace()가 전체 호출 스택을 순회
- 너무 광범위한 동기화 - 하나의 락이 병목 현상이 됨
- 재사용 가능한 객체 재생성 - 호출마다 ObjectMapper, DateTimeFormatter, Gson 생성
- 가상 스레드 고정 (JDK 21-23) - synchronized + 블로킹 I/O가 캐리어를 고정
상세 예시 및 해결 방법
1. 루프 내 문자열 연결
문제 코드:
String report = "";
for (String line : logLines) {
report = report + line + "\n";
}문자열 불변성으로 인해 O(n²) 복사가 발생합니다. BellSoft JMH 벤치마크에 따르면 n이 4배 증가할 때 루프 연결 속도는 7배 이상 느려집니다.
해결 방법:
StringBuilder sb = new StringBuilder();
for (String line : logLines) {
sb.append(line).append("\n");
}
String report = sb.toString();참고: JDK 9부터 컴파일러는 "Order: " + id + " total: " + amount와 같은 단일 라인 연결을 최적화하지만, 이 최적화는 루프 내부로 이어지지 않습니다.
2. 루프 내 스트림으로 인한 의도치 않은 O(n²)
문제 코드:
for (Order order : orders) {
int hour = order.timestamp().atZone(ZoneId.systemDefault()).getHour();
long countForHour = orders.stream()
.filter(o -> o.timestamp().atZone(ZoneId.systemDefault()).getHour() == hour)
.count();
ordersByHour.put(hour, countForHour);
}이 패턴은 JFR 기록에서 CPU 스택 샘플의 거의 71%를 차지했습니다. 10,000건의 주문으로 단일 패스 대신 1억 건의 비교를 수행합니다.
해결 방법:
for (Order order : orders) {
int hour = order.timestamp().atZone(ZoneId.systemDefault()).getHour();
ordersByHour.merge(hour, 1L, Long::sum);
}이는 단일 패스로 O(n) 성능을 제공합니다. 단일 스트림 파이프라인에서 Collectors.groupingBy(... Collectors.counting())을 사용할 수도 있습니다.
이 글은 3부작 Java 성능 최적화 시리즈의 1부이며, 2부와 3부는 곧 출시될 예정입니다. 2부에서는 화염 그래프와 실제로 핫했던 메서드를 포함하여 이러한 수치 뒤의 프로파일링 데이터를 살펴볼 것입니다.
📖 전체 Source 읽기: HN AI Agents
👀 See Also

클로드와 OpenAI 사용을 위한 모델 라우팅 기준선
한 개발자가 Claude Haiku 4.5, Sonnet 4.6, Opus 4.6 및 ChatGPT 5.3 Codex를 다양한 작업 유형에 사용하는 모델 라우팅 전략을 공유하며, 필요 시 GPT-5 Mini와 GPT-5.4로 폴백합니다.

OpenClaw Docker 사용자: 깨진 Discord 및 채널 확장 기능을 수정하려면 커밋 0c926a2c5로 고정하세요.
Docker를 통해 OpenClaw를 업데이트한 후 Discord, Signal, WhatsApp과 같은 채널 확장 기능이 모듈 가져오기 오류로 실패합니다. 이 문제는 커밋 d9c285e93과 두 번째 Docker 특정 버그에서 비롯됩니다. 안정적인 해결책을 위해 커밋 0c926a2c5로 고정하세요.

MCP를 통해 기계적 작업을 DeepSeek V4 Flash에 오프로드하여 Claude 비용 60배 절감
한 Reddit 사용자가 파일 분류, JSON 재포맷, 필드 추출 작업을 간단한 MCP 도구와 CLAUDE.md 거부 목록 규칙을 통해 DeepSeek V4 Flash로 라우팅하여 Claude API 사용량을 60배 절감했습니다.

종단 간 LLM 스택 추적: 키 입력부터 스트리밍된 토큰까지
소프트웨어 엔지니어가 LLM에 프롬프트를 보낼 때 스택의 모든 계층에서 발생하는 일을 추적하는 포괄적인 문서를 작성했습니다. 클라이언트 측 토큰 카운팅, 네트워크 프로토콜, API 게이트웨이, 안전 분류기, 토큰화, KV 캐시, 샘플링 파이프라인, 스트리밍 메커니즘을 다룹니다.