기타
[2차시] 애플리케이션 구동 시간 단축하기 : 빠른 스타트업을 위한 전략과 기법
킨글
2025. 3. 17. 22:18
어플리케이션 구동 개요
- 1 - JVM 시작
- 클래스 로딩 : 어플리케이션 클래스와 라이브러리 로드
- 메모리 할당 : 힙(Heap) 메모리와 스택(Stack) 메모리 초기화
- JVM 옵션 설정 : 성능 최적화를 위한 옵션 설정 (예 : -Xmx, -Xms)
- 2 - Spring Application 클래스 실행 단계
- 어플리케이션의 시작점을 정의
- 구성 설정과 리스너 등록
- 배너 출력 및 로깅 설정
- 3 - 환경 설정 단계
- 메서드 호출 시 지역 변수 저장
- 메서드 호출 및 반환 시 스택 프레임 추가 및 제거
- Stack 메모리의 한계 (고정 크기)
- 4 - 어플리케이션 컨텍스트 생성 단계
- Application Context 생성
- 스프링의 DI(Dependency Injection)를 통한 Bean 주입
- Bean 라이프사이클 관리
- 5 - Bean 등록 및 초기화
- Bean 등록 및 의존성 주입
- Bean의 초기화 메서드 실행
- Bean 순환 참조 나 지연 초기화 최적화
- 6 - 어플리케이션 서버(WAS) 시작
- 내장 톰캣, Jetty, Undertow 등의 웹 서버 실행
- HTTP 포트 및 SSL 설정 적용
- Servlet 컨테이너 초기화 및 요청 처리 준비
- 7 - 어플리케이션 컨텍스트 완료 및 준비 상태
- 모든 Bean 초기화 및 의존성 주입 완료
- Application Ready Event 발생
- 어플리케이션은 요청을 처리할 준비가 완료됨
라이브러리 의존성 관리를 통한 최적화
- 의존성 트리 분석 및 버전 관리
- 의존성 트리를 분석해 상호 의존 관계 최적화
- 중복 의존성 제거
- 최신 버전의 라이브러리를 사용해 성능 최적화
- ./gradlew dependencies # 의존성 트리 확인
Lazy Initialization을 통한 최적화
- 스프링 부트의 기본 Bean 초기화
- 기본적으로 즉시 초기화로 동작
- 어플리케이션 시작 시 모든 빈을 미리 로딩
- 빈의 수가 많아질수록 구동 시간이 증가
- Lazy Initialization 설정 방법
- 어플리케이션 전체에 적용
- spring.main.lazy-initialization=true
- 개별 빈에만 적용
- @Lazy 어노테이션 추가
- 어플리케이션 전체에 적용
- Lazy Initialization의 장단점
- 장점
- 어플리케이션 시작 시간 단축
- 사용되지 않는 빈의 초기화를 방지해 리소스 절약
- 단점
- 빈을 사용할 때 지연 로딩 시간 발생
- 특정 시점에서 성능 오버헤드가 생길 수 있음
- 장점
- Lazy Initialization 최적화 전략
- 자주 사용되지 않는 Bean에 적용
- 대규모 모듈이나 외부 서비스 연동 Bean에 적용
- 테스트 환경에서 사용 시 유용
- Lazy Initialization과 빈 순환 참조 문제
- 두 빈이 서로 순환 참조를 갖는 경우
- Lazy Initialization으로 인해 순환 참조 발생 시 예외 가능성
- 순환 참조 문제 해결을 위한 설계 개선 필요
- Lazy Initialization의 대안 : Application Context 초기화
- 프로파일을 통해 환경별로 빈 초기화 제어
- Application ContextInitializer를 통한 컨텍스트 초기화 최적화
- Bean 로딩 조건을 세분화해 필요 없는 빈의 초기화 방지
Fat JAR, AOT, CDS
- Fat JAR란?
- 모든 종속성을 포함한 단일 JAR 파일
- 스프링 부트에서 기본적으로 제공
- 독립 실행 가능한 어플리케이션 배포에 적합
- 배포와 실행이 간편하지만, JAR 크기와 구동 시간에 영향을 미침
- Fat JAR 장점
- 종속성이 모두 포함되어 있어 일관된 실행 환경 제공
- 배포가 간편하고 단일 파일로 배포 가능
- 서버 설정이나 라이브러리 설치 없이 실행 가능
- Fat JAR 단점
- JAR 크기 증가 : 모든 종속성을 포함하여 파일 크기 증가
- 불필요한 라이브러리 포함 가능성
- JAR 크기에 따라 어플리케이션 시작 시간 증가
- Layered JAR를 통한 Fat JAR 최적화
- Layered JAR : JAR 파일을 여러 계층으로 분리
- 어플리케이션 코드와 종속성을 별도 레이어로 관리
- 캐시 가능 : 종속성이 변경되지 않으면 캐싱을 통해 빌드 속도 향상
- 특히, Docker 이미지 빌드에서 유용
- Docker에서의 Fat JAR 최적화
- Layered JAR와 Docker의 이미지 레이어 분리
- Docker 이미지에서 종속성과 어플리케이션 레이어를 독립적으로 관리
- 종속성 캐싱을 통해 Docker 이미지 빌드 속도 최적화
FROM open jdk:11-jre-slim
COPY build/libs/app.jar /app/app.jar
WORKDIR/app
ENTRYPOINT ["java", "-jar", "app.jar"]
- AOT 컴파일 개요
- JIT 컴파일
- 런타임에 코드가 실행될 때 동적으로 컴파일
- 실행 시점에서 최적화를 수행
- 초기 구동 시간은 느리지만, 장기적으로 성능이 좋아질 수 있음
- 장점
- 빠른 시작 시간 : 초기 로딩 과정에서 컴파일 시간을 절약
- 메모리 사용량 감소 : 미리 컴파일된 코드를 사용해 런타임 메모리 절감
- 네이티브 이미지 : Spring Native로 경량화된 어플리케이션 생성
- 클라우드 및 서버리스 환경에서 유리
- AOT 컴파일
- 실행 전에 코드를 미리 컴파일하여 성능 최적화
- 스프링 부트에서 Spring Native를 사용해 AOT 컴파일 적용 가능
- 구동 시점에서 빠르게 실행 가능
- 메모리 사용량 감소 및 빠른 시작 시간 제공
- 실행 전에 코드를 미리 컴파일하여 성능 최적화
- JIT 컴파일
- Spring Native로 AOT 컴파일 적용
- Spring Native : 스프링 어플리케이션을 네이티브 이미지로 변환
- GraalVM : Spring Native가 GraalVM을 사용해 AOT 컴파일
- 네이티브 이미지로 변환된 어플리케이션은 JVM 없이 실행 가능
dependencies {
implementation("org.springframework.experimental:spring-native")
}
task.withType<JavaCompile> {
options.compilerArgs.add("-parameters")
}
- CDS 개념 및 기본 원리
- JVM의 클래스 메타데이터를 공유 아카이브에 저장
- 여러 JVM 프로세스가 동일한 클래스를 공유
- 클래스 로딩 시간을 줄여 어플리케이션 구동 시간을 단축
- 메모리 절약 : 클래스 데이터를 공유하여 중복 로딩 방지
- 장점
- 빠른 JVM 시작 시간 : 자주 사용하는 클래스의 로딩 시간을 단축
- 메모리 절약 : 여러 JVM 인스턴스에서 클래스 데이터를 공유하여 중복 로딩 방지
- 자바 기반의 마이크로서비스 환경에서 특히 유용
- 대규모 서버 어플리케이션에서 메모리와 성능 최적화
- CDS 적용 방법
- Shared Archive 생성
- JVM에 아카이브 파일을 지정하여 CDS 활성화
- 스프링 부트 어플리케이션에서의 적용 예시
- 쉽게 말해서 자주 사용하는 클래스 정보를 미리 저장해두고 JVM이 이를 바로 활용하도록 만드는 방식
java -Xshare:dump-XX:SharedArchiveFile=app-cds.jsa -jar app.jar
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -jar app.jar
- 스프링 부트에서의 CDS 적용
- 스프링 부트 어플리케이션의 빈 로딩 최적화
- 빈 정의 및 초기화에 대한 메타데이터 공유
- Spring Native와 함께 CDS를 사용해 더 빠른 시작 시간 구현
- Dynamic CDS와 CDS의 차이
- CDS : 고정된 클래스를 미리 아카이브에 저장하여 공유
- Dynamic CDS : 동적으로 로드되는 클래스를 아카이브에 추가 가능
- 어플리케이션 실행 중에도 아카이브 갱신 가능