자바/자바

인프런 강의 정리_ JAVA 성능 튜닝과 트러블 슈팅 * 휴먼넷

불광동 물주먹 2025. 8. 31. 01:47

자바 성능 튜닝 & 메모리/리소스 관리 핵심 가이드

대규모 트래픽이나 장기 서비스 운영 환경에서 자바 애플리케이션은 메모리 관리, 스레드 처리, 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. 에러/장애 분석 가이드

  1. Exception 로그 분석
  2. 해결 안 되면 Thread Dump로 데드락/스레드 상태 확인
  3. 그래도 원인 불명 → Heap Dump 분석 (OOM 확인)

 

14. 힙 덤프 분석 (Eclipse MAT 활용)

  • OOM 원인 유형
    • 메모리 Leak
      • Cache Leak (캐시에 쌓이고 제거 안됨)
      • Pool Leak (Connection Pool 반환 누락)
    • 순간적 OOM
      • 과도한 데이터 조회
      • 잘못된 문자열 처리 (대량 String 조작 → char[] 메모리 폭발)
  • 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 트랜잭션/배치 처리로 성능 개선
  • 캐시 도입, 톰캣 파라미터 최적화, 로그 관리