1. 개요

JVM은 지금까지 구축된 가장 오래되었지만 강력한 가상 머신 중 하나입니다.

이 기사에서는 JVM을 워밍업하는 것이 무엇을 의미하고 어떻게 수행하는지 간략하게 살펴봅니다.

2. JVM 아키텍처 기본 사항

새 JVM 프로세스가 시작될 때마다 필요한 모든 클래스가 ClassLoader 인스턴스에 의해 메모리에 로드됩니다 . 이 프로세스는 세 단계로 진행됩니다.

  1. 부트스트랩 클래스 로딩: " 부트스트랩 클래스 로더 "는 Java 코드와 java.lang.Object 와 같은 필수 Java 클래스 를 메모리에 로드합니다. 이러한 로드된 클래스는 JRE\lib\rt.jar 에 있습니다.
  2. Extension Class Loading : ExtClassLoader는 java.ext.dirs 경로에 있는 모든 JAR 파일을 로드하는 역할을 합니다. 개발자가 JAR을 수동으로 추가하는 비 Maven 또는 비 Gradle 기반 애플리케이션에서는 이 단계에서 모든 클래스가 로드됩니다.
  3. 애플리케이션 클래스 로딩 : AppClassLoader는 애플리케이션 클래스 경로에 있는 모든 클래스를 로드합니다.

이 초기화 프로세스는 지연 로딩 체계를 기반으로 합니다.

3. JVM 워밍업이란?

클래스 로딩이 완료되면 모든 중요한 클래스(프로세스 시작 시 사용됨)가 JVM 캐시(네이티브 코드) 로 푸시되어 런타임 중에 더 빠르게 액세스할 수 있습니다. 다른 클래스는 요청별로 로드됩니다.

Java 웹 애플리케이션에 대한 첫 번째 요청은 종종 프로세스 수명 동안의 평균 응답 시간보다 상당히 느립니다. 이 워밍업 기간은 일반적으로 Lazy 클래스 로딩 및 JIT(Just-In-Time) 컴파일 때문일 수 있습니다.

대기 시간이 짧은 애플리케이션의 경우 런타임에 액세스할 때 즉시 사용할 수 있도록 모든 클래스를 미리 캐시해야 합니다.

이 JVM 조정 프로세스를 워밍업이라고 합니다.

4. 계층화된 편집

JVM의 건전한 아키텍처 덕분에 애플리케이션 수명 주기 동안 자주 사용되는 메서드가 기본 캐시에 로드됩니다.

이 속성을 사용하여 응용 프로그램이 시작될 때 중요한 메서드를 캐시에 강제 로드할 수 있습니다. 이를 위해서는 Tiered Compilation 이라는 VM 인수를 설정해야 합니다 .

-XX:CompileThreshold -XX:TieredCompilation

일반적으로 VM은 인터프리터를 사용하여 컴파일러에 제공되는 메서드에 대한 프로파일링 정보를 수집합니다. 계층 구조에서는 인터프리터 외에도 클라이언트 컴파일러를 사용하여 자신에 대한 프로파일링 정보를 수집하는 컴파일된 버전의 메서드를 생성합니다.

컴파일된 코드는 해석된 코드보다 상당히 빠르기 때문에 프로파일링 단계에서 프로그램이 더 나은 성능으로 실행됩니다.

이 VM 인수가 활성화된 JBoss 및 JDK 버전 7에서 실행되는 애플리케이션은 문서화된 버그 로 인해 일정 시간이 지나면 충돌하는 경향이 있습니다 . 이 문제는 JDK 버전 8에서 수정되었습니다.

여기서 주목해야 할 또 다른 점은 로드를 강제하려면 실행할 모든(또는 대부분의) 클래스에 액세스해야 한다는 점입니다. 단위 테스트 중에 코드 적용 범위를 결정하는 것과 비슷합니다. 코드가 많을수록 성능이 향상됩니다.

다음 섹션에서는 이를 구현하는 방법을 보여줍니다.

5. 수동 구현

JVM을 준비하는 대체 기술을 구현할 수 있습니다. 이 경우 간단한 수동 워밍업에는 응용 프로그램이 시작되자마자 다른 클래스 생성을 수천 번 반복하는 작업이 포함될 수 있습니다.

먼저 일반적인 방법으로 더미 클래스를 만들어야 합니다.

public class Dummy {
    public void m() {
    }
}

다음으로, 응용 프로그램이 시작되자마자 최소 100,000번 실행될 정적 메서드가 있는 클래스를 만들어야 합니다. 실행될 때마다 이전에 만든 더미 클래스의 새 인스턴스를 만듭니다.

public class ManualClassLoader {
    protected static void load() {
        for (int i = 0; i < 100000; i++) {
            Dummy dummy = new Dummy();
            dummy.m();
        }
    }
}

이제 성능 향상을 측정 하기 위해 메인 클래스를 만들어야 합니다. 이 클래스에는 ManualClassLoader의 load() 메서드에 대한 직접 호출이 포함된 하나의 정적 블록이 포함되어 있습니다.

기본 함수 내에서 ManualClassLoader의 load() 메서드를 한 번 더 호출하고 함수 호출 직전과 직후에 시스템 시간을 나노초 단위로 캡처합니다. 마지막으로 실제 실행 시간을 얻기 위해 이 시간을 뺍니다.

애플리케이션을 두 번 실행해야 합니다. 정적 블록 내 에서 load() 메서드 호출로 한 번, 이 메서드 호출 없이 한 번:

public class MainApplication {
    static {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Warm Up time : " + (end - start));
    }
    public static void main(String[] args) {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Total time taken : " + (end - start));
    }
}

아래 결과는 나노초 단위로 재현됩니다.

워밍업으로 워밍업 없음 차이점(%)
1220056 8903640 730
1083797 13609530 1256년
1026025 9283837 905
1024047 7234871 706
868782 9146180 1053

예상대로 웜업 방식을 사용하면 일반 방식보다 훨씬 나은 성능을 보여줍니다.

물론 이것은 매우 단순한 벤치마크 이며 이 기술의 영향에 대한 표면 수준의 통찰력만 제공합니다. 또한 실제 응용 프로그램에서는 시스템의 일반적인 코드 경로로 예열해야 한다는 점을 이해하는 것이 중요합니다.

6. 도구

또한 여러 도구를 사용하여 JVM을 준비할 수 있습니다. 가장 잘 알려진 도구 중 하나는 JMH 인 Java Microbenchmark Harness 입니다. 일반적으로 마이크로 벤치마킹에 사용됩니다. 일단 로드되면 반복적으로 코드 스니펫을 적중하고 워밍업 반복 주기를 모니터링합니다.

이를 사용하려면 pom.xml 에 다른 의존성을 추가해야 합니다 .

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
</dependency>

Central Maven Repository 에서 최신 JMH 버전을 확인할 수 있습니다 .

또는 JMH의 maven 플러그인을 사용하여 샘플 프로젝트를 생성할 수 있습니다.

mvn archetype:generate \
    -DinteractiveMode=false \
    -DarchetypeGroupId=org.openjdk.jmh \
    -DarchetypeArtifactId=jmh-java-benchmark-archetype \
    -DgroupId=com.baeldung \
    -DartifactId=test \
    -Dversion=1.0

다음으로 기본 메서드를 만들어 보겠습니다.

public static void main(String[] args) 
  throws RunnerException, IOException {
    Main.main(args);
}

이제 메서드를 만들고 JMH의 @Benchmark 어노테이션으로 어노테이션을 추가해야 합니다.

@Benchmark
public void init() {
    //code snippet	
}

init 메서드 내에서 워밍업을 위해 반복적으로 실행해야 하는 코드를 작성해야 합니다.

7. 성능 벤치마크

지난 20년 동안 Java에 대한 대부분의 기여는 GC(Garbage Collector) 및 JIT(Just In Time Compiler)와 관련되었습니다. 온라인에서 발견되는 거의 모든 성능 벤치마크는 일정 시간 동안 이미 실행 중인 JVM에서 수행됩니다. 하지만,

그러나 Beihang University 는 JVM 준비 시간을 고려한 벤치마크 보고서를 발표했습니다. 그들은 대규모 데이터를 처리하기 위해 Hadoop 및 Spark 기반 시스템을 사용했습니다.

jvm

여기서 HotTub은 JVM이 예열된 환경을 지정합니다.

보시다시피 속도 향상은 특히 상대적으로 작은 읽기 작업의 경우 중요할 수 있습니다. 이것이 이 데이터를 고려하는 것이 흥미로운 이유입니다.

8. 결론

이 빠른 기사에서는 애플리케이션이 시작될 때 JVM이 클래스를 로드하는 방법과 성능 향상을 위해 JVM을 워밍업하는 방법을 보여주었습니다.

계속하려는 경우 이 책 에서 주제에 대한 자세한 정보와 지침을 살펴봅니다.

그리고 항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner