자바 성능 튜닝 & 메모리/리소스 관리 핵심 가이드
대규모 트래픽이나 장기 서비스 운영 환경에서 자바 애플리케이션은 메모리 관리, 스레드 처리, DB 접근 방식 등에 따라 성능이 크게 달라질 수 있습니다. 아래는 실무에서 자주 발생하는 문제와 해결 가이드를 정리한 내용입니다.
1. 함수 사용 시 주의
- 재귀함수: 깊은 호출로 인해 힙 메모리 초과(OutOfMemoryError) 발생 가능.
- 스레드 점유: 특정 쓰레드가 계속 점유하고 있으면 GC가 객체를 완전히 회수하지 못하는 경우가 생김 → 불필요한 객체 참조를 끊어주는 습관 필요.
2. static 남용 주의
- static 객체는 GC 대상에서 제외되므로 한 번 메모리에 올라가면 애플리케이션 종료 시까지 유지.
- 유틸성 클래스가 아니라면 불필요한 static 필드 선언은 피하고, 의존성 주입(DI)으로 대체하는 것이 바람직함.
3. 클래스 구조와 메모리
- 하나의 클래스에 이너 클래스, 불필요한 멤버를 무분별하게 두면 클래스 로딩 시 메모리 낭비 발생.
- 대규모 데이터 처리용 클래스는 작게 분리해서 필요할 때만 로딩되도록 설계하는 것이 좋음.
4. 디자인 패턴 활용
- 싱글톤 패턴: ThreadPool, DB Connection Pool, Object Pool 등 재사용이 중요한 리소스 관리에 필수.
- 잘못 구현된 싱글톤은 메모리 릭을 유발할 수 있으므로 enum 기반 싱글톤을 권장.
5. 파일 처리
- 잘못된 방식: 큰 파일을 한 번에 읽어 List<String> 등에 담는 것. (→ OOM 위험)
- 권장 방식:
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { // 라인 단위 처리 } }
즉, **스트리밍 방식(line 단위 처리)**으로 읽어야 메모리 효율적.
6. 힙 사이즈 설정
- -Xmx, -Xms 옵션으로 JVM 힙 크기를 조정 가능.
- 힙이 너무 작으면 GC가 자주 발생 → CPU 낭비.
- 너무 크면 GC 주기가 늘어나지만, 한 번 발생할 때 STW(Stop-The-World) 시간이 길어짐.
- 따라서 서비스 특성(요청량, 객체 생성 패턴)에 맞게 튜닝해야 함.
7. 문자열 처리
- String은 불변(Immutable) 객체.
- 반복적인 문자열 결합은 StringBuilder 또는 멀티스레드 환경에서는 StringBuffer 사용
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
8. DB 성능 최적화
- 트랜잭션: @Transactional 미사용 시 insert마다 commit 발생 → 성능 저하.
- 대량 Insert:
- MyBatis의 foreach 문 사용 가능.
- 단, SQL 파라미터 길이 제한이 있으므로 일정 단위로 나눠서 처리 필요.
9. 멀티쓰레드 처리
- CPU 코어 수 × (2~4) ≈ ThreadPool 기본값으로 설정.
- 공유 자원 접근 시 동기화 비용을 최소화하도록 설계 (ex: ConcurrentHashMap).
10. 캐싱 전략
- DB/외부 API 호출 결과는 캐싱을 활용해 불필요한 I/O 감소.
- Redis, Caffeine Cache 등을 상황에 맞게 사용.
11. 톰캣(Tomcat) 튜닝 핵심
- maxThreads: 동시에 실행 가능한 워커 스레드 수.
- maxConnections: 동시에 열어둘 수 있는 TCP 연결 수.
- acceptCount: maxThreads가 다 찼을 때 대기 가능한 요청 수.
🔑 가이드라인:
- maxConnections >= maxThreads × (5~20)
- acceptCount ≈ maxThreads × (0.5~2)
- keepAliveTimeout은 짧게(5~15초) → 유휴 연결 축소
- ulimit -n (파일 디스크립터 수)도 함께 조정 필요.
12. 로그 관리
- 로그 레벨이 TPS에 직접적인 영향을 줌 (debug → info 전환 시 성능 차이 발생).
- 불필요한 로그 출력 최소화 필요.
- I/O를 동반하는 로그는 비동기 처리(AsyncAppender) 권장.
13. 에러/장애 분석 가이드
- Exception 로그 분석
- 해결 안 되면 Thread Dump로 데드락/스레드 상태 확인
- 그래도 원인 불명 → Heap Dump 분석 (OOM 확인)
14. 힙 덤프 분석 (Eclipse MAT 활용)
- OOM 원인 유형
- 메모리 Leak
- Cache Leak (캐시에 쌓이고 제거 안됨)
- Pool Leak (Connection Pool 반환 누락)
- 순간적 OOM
- 과도한 데이터 조회
- 잘못된 문자열 처리 (대량 String 조작 → char[] 메모리 폭발)
- 메모리 Leak
- Eclipse Memory Analyzer Tool(MAT)로 객체 그래프 확인, 참조 체인(Reference Chain) 분석.
15. 대용량 List 다룰 때
- ArrayList는 내부적으로 버퍼 확장 시 기존 크기의 1.5배 정도로 증가.
- 대량 데이터를 담을 게 예상된다면 초기 용량(capacity) 지정이 메모리 효율적
List<String> list = new ArrayList<>(100000); // 미리 capacity 지정
핵심 요약
- 불필요한 객체 참조 제거 → GC 효율 ↑
- static 남용 주의
- 스트리밍 방식의 파일 처리
- 적절한 힙 사이즈와 GC 튜닝
- StringBuilder/Buffer 적극 활용
- DB 트랜잭션/배치 처리로 성능 개선
- 캐시 도입, 톰캣 파라미터 최적화, 로그 관리
'자바 > 자바' 카테고리의 다른 글
| (자바) 상속 정리 *인프런_널널한 개발자 (0) | 2025.09.08 |
|---|---|
| (자바) 인프런 강의(자바 ) *널널한 개발자 (0) | 2025.09.02 |
| (자바)자바 제너릭 (Generics) 정리 (0) | 2025.08.21 |
| (자바)정렬 Comparator vs Comparable 완전 정리 (1) | 2025.08.07 |
| (자바) equals(), hashCode(), toString()의 역할 (0) | 2025.07.07 |