Java 21 JVM Option

Java 21 JVM Option 설정 튜닝

Problem

JVM 옵션 튜닝 포인트

  • 메모리 설정:
    • 초기 힙 크기가 작아 동적 증가 시 오버헤드 발생
    • NewRatio 설정으로 인한 불필요한 메모리 조정
  • GC 설정:
    • ZGC는 2GB 정도의 힙 크기에서는 오버스펙
    • STW(Stop-The-World) 시간 제한이 없어 예측 불가능한 지연 발생
  • 로깅 설정:
    • 구버전/신버전 설정 혼용으로 인한 로깅 무효화
    • 불필요한 로그 정보 수집

개선 목표

  • 안정성:
    • 예측 가능한 GC 동작
    • 안정적인 메모리 사용
  • 성능:
    • 최소한의 GC 오버헤드
    • 효율적인 메모리 활용
  • 모니터링:
    • 명확한 GC 로그 수집
    • 문제 발생 시 빠른 원인 파악

Solution

기존 설정

## Configuration ###############################################################
JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx2048m -XX:NewRatio=2"
JAVA_OPTS="$JAVA_OPTS -Xss32m"
JAVA_OPTS="$JAVA_OPTS -XX:SurvivorRatio=4"
JAVA_OPTS="$JAVA_OPTS -XX:+UseZGC"
JAVA_OPTS="$JAVA_OPTS -Xlog:safepoint=debug"
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails"
JAVA_OPTS="$JAVA_OPTS -verbose:gc -Xloggc:$GCLOG"
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*"
JAVA_OPTS="$JAVA_OPTS -Xlog:gc+heap=debug"
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=$GCDIR/$SERVICE_NAME-java_pid.hprof"
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8"

신규 설정

## Configuration ###############################################################
# 1. 힙 메모리 설정 변경
#JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx2048m -XX:NewRatio=2"
JAVA_OPTS="$JAVA_OPTS -Xmx2048m -Xmx2048m"

# 2. 스레드 스택 크기 설정 제거
# JAVA_OPTS="$JAVA_OPTS -Xss32m"

# 3. GC 설정 변경
#JAVA_OPTS="$JAVA_OPTS -XX:+UseZGC"
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"

# 4. GC 로깅 설정 통합
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*=info,gc+heap=debug,safepoint=debug:file=$GCLOG:time,level,tags"
#JAVA_OPTS="$JAVA_OPTS -Xlog:safepoint=debug"
#JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails"
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*"
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc+heap=debug"
#JAVA_OPTS="$JAVA_OPTS -verbose:gc -Xloggc:$GCLOG"

JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=$GCDIR/$SERVICE_NAME-java_pid.hprof"
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8"

변경 사항 정리

1. 힙 메모리 설정 변경

  • ASIS: -Xms512m -Xmx2048m -XX:NewRatio=2
  • TOBE: -Xmx2048m -Xmx2048m
  • 변경 이유:
    • 하나의 VM에 하나의 Java 인스턴스만 실행되므로 초기 힙 크기를 낮게 설정할 필요가 없음
    • 동적 힙 증가 시 발생하는 오버헤드 방지
    • G1GC는 New/Old 비율을 자동으로 조절하므로 NewRatio 설정 불필요

2. 스레드 스택 크기 설정 제거

  • ASIS: -Xss32m
  • TOBE: 제거 (기본값 사용)
  • 변경 이유:
    • 스레드당 32MB의 큰 스택 메모리는 리소스 낭비
    • Virtual Thread 사용 시 더욱 위험한 설정
    • 기본값 사용이 더 안전하고 효율적

3. GC 설정 변경

  • ASIS: -XX:+UseZGC
  • TOBE: -XX:+UseG1GC -XX:MaxGCPauseMillis=200
  • 변경 이유:
    • ZGC는 수십~수백GB의 대용량 힙에서 적합
    • 2GB 정도의 힙 크기에서는 G1GC가 더 효율적
    • MaxGCPauseMillis로 STW 시간 제한 설정

4. GC 로깅 설정 통합

  • ASIS: 여러 개의 구버전 GC 로깅 설정 혼용
  • TOBE: -Xlog:gc*=info,gc+heap=debug,safepoint=debug:file=$GCLOG:time,level,tags
  • 변경 이유:
    • 구버전/신버전 설정 혼용 시 신버전 설정이 무효화될 수 있음
    • 통합된 로깅 설정으로 관리 용이성 향상
    • 필요한 로그 레벨과 정보만 선택적으로 수집

5. 유지된 설정

  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=$GCDIR/$SERVICE_NAME-java_pid.hprof
  • -Dfile.encoding=UTF8

번외: logback.xml > logback-spring.xml

  • Spring Boot 애플리케이션에서 logback.xml > logback-spring.xml 변경하는 이유

1. Spring Boot의 고급 기능 지원

  • logback-spring.xml 사용하면 Spring Boot 특별한 기능 활용 가능
  • <springProperty>, <springProfile> Spring Boot 전용 태그 사용 가능
  • application.propertiesapplication.yml 정의된 프로퍼티 로깅 설정 직접 참조 가능

2. 프로파일 기반 설정

  • logback-spring.xml 은 Spring 프로파일 기능을 활용 가능하여 환경별로 다른 로깅 설정 적용 가능
  • 개발, 테스트, 운영 환경에 따라 다른 로그 레벨이나 출력 방식 설정

3. Spring Boot의 자동 설정과의 통합

  • logback-spring.xml 은 Spring Boot 자동 설정 메커니즘과 높은 상호 호환성 지원
  • Spring Boot 기본 로깅 설정을 오버라이드 & Spring Boot 다른 기능들과 충돌 없이 동작 가능
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">

    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

    <springProperty scope="context" name="logPath" source="logging.file.path" defaultValue="/home/ktc/log"/>
    <springProperty scope="context" name="logFileName" source="logging.file.name" defaultValue="${USER}-${HOSTNAME}-${INSTANCE_NAME}"/>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/${logFileName}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/${logFileName}-%d{yyyyMMddHH}-%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>

        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS}.%thread> %-5level T[%X{correlationId}] U[%X{userId}] M[%X{mpaId}] - %msg%n
            </pattern>
        </encoder>
    </appender>

    <springProfile name="dev">
        <logger name="com.kona.ktc" level="DEBUG"/>
        <logger name="org.springframework" level="INFO"/>
        <logger name="org.springframework.data.redis" level="DEBUG"/>
        <logger name="org.springframework.web" level="DEBUG"/>
        <logger name="io.netty" level="INFO"/>
    </springProfile>

    <springProfile name="qa">
        <logger name="com.kona.ktc" level="DEBUG"/>
        <logger name="org.springframework" level="INFO"/>
        <logger name="org.springframework.data.redis" level="DEBUG"/>
        <logger name="org.springframework.web" level="DEBUG"/>
        <logger name="io.netty" level="INFO"/>
    </springProfile>

    <springProfile name="prod">
        <logger name="com.kona.ktc" level="INFO"/>
        <logger name="org.springframework" level="WARN"/>
        <logger name="org.springframework.data.redis" level="WARN"/>
        <logger name="org.springframework.web" level="WARN"/>
        <logger name="io.netty" level="WARN"/>
    </springProfile>

    <root level="INFO">
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
</configuration>