JVM

  • JVM은 바이트 코드를 실행하는 가상 머신

  • 코드 작성(Main.kt) → (컴파일) → 바이트 코드(Main.class) → (런타임) → 기계어 → JVM에서 실행

  • JVM에서 바이트 코드들을 기계어로 변환하는 단계를 거치는게 이 작업을 하는 컴파일러가 JIT 컴파일러

  • 기본적으로 메모리를 stack, heap으로 관리함

컴파일러

  • 컴파일러에서 프로그램 코드를 기계어로 변환할 수 있는 시점은 두 개.

    • 프로그램 실행 전 → 정적 컴파일러

    • 프로그램 실행 중 → 동적 컴파일러(인터프리트 언어)

      • 인터프리터

  • C, C++과 같이 실행 전에 모두 컴파일하면 컴파일 시간이 길지만 런타임에 성능이 뛰어남

  • js, py 같이 실행중에 컴파일하면 컴파일 시간이 필요없지만 런타임 중 리소스 일부를 컴파일에 사용하기 때문에 프로그램 성능이 떨어질 수 있음

  • 이 둘의 단점을 최소화하고 장점을 극대화하는 방향으로 설계된 컴파일러가 JIT 컴파일러

JIT

  • Just-in-Time Compilers

  • 소스 코드를 바이트 코드를 만들 때는 정적 컴파일러를 사용

  • 바이트 코드를 기계어로 변환할 땐 동적 컴파일러가 아닌 JIT 컴파일러를 사용

  • 런타임에 기계어로 변환된 코드를 캐시에 저장하여 재사용시 컴파일하기 않고 그대로 사용

    • 캐시된 코드를 사용하면 마지 정적 컴파일과 같은 효과

  • 하지만, JVM의 캐시 공간은 매우 작으므로 모두 캐싱하지 않음

    • 자주 수행되는 코드를 선별함

  • JIT 컴파일러 내부에 C1, C2 2가지 컴파일러가 있음

    • C1 컴파일러: 런타임에 바이트 코드를 기계어로 변환하는 과정만 수행

    • C2 컴파일러: 런타임에 파이트 코드를 기계어로 변환한 다음 캐시에 저장하는 과정 수행

    • 수행 빈도와 복잡도에 따라 4가지 레벨로 분류하여 코드를 수행

      • 이 때 1~3 레벨 코드는 C1 컴파일러를 사용하고 캐싱 안함

      • 4레벨 코드는 C2 컴파일러를 사용하여 캐싱함

  • 실제 수행을 보기 위해서 java 파일 실행시 -XX:+PrintCompilation 옵션 추가

    • 3열이 코드 레벨

  • 어플리케이션이 커져서 코드를 캐시할 수 있는 메모리 공간이 없다면 C2 컴파일러는 더이상 효율적으로 동작하지 않음

    • 이 때 코드 캐시 사이즈를 확인하고 튜닝이 필요함

      • java 실행 시 -XX:+PrintCodeCache 옵션 추가

Code Cache

컴파일된 메서드의 네이티브 코드 생성을 저장하는데 사용되는 영역.

코드 캐쉬는 거의 성능문제를 발생시키지 않지만, 한번 코드 캐쉬 문제가 생기면 그 효과는 어마어마할 것이다. 만약 코드 캐쉬 메모리를 모두 사용하게 되면, JVM 은 경고 메시지를 출력하고 “interpreted-only mode” 로 전환한다. JIT 컴파일러는 정지되고 더이상 바이트코드(bytecode)는 네이티브 코드로 컴파일 될지 않을 것이다. 따라서 애클리케이션이 동작은 계속되지만 크기의 순서에따라 점점 느려진다.

Option Description

InitialCodeCacheSize

초기 코드 캐시 사이즈.
코드 캐시 사이즈가 증가할 때마다 overhead가 발생. 미리 크게 늘려놓고 사용하여 이 비용을 없앨 수 있을 것으로 보임.

ReservedCodeCacheSize

예약된 코드 캐시 사이즈(최댓값)

CodeCacheExpansionSize

코드 캐시 확장되는 단위 크기

CICompilerCount

컴파일러 스레드 개수.
요것까지 건드리는 것은 하지 않는 것이 좋을 것 같다..

XX:CompileThreshold

서버 컴파일러의 기본값은 10,000
어차피 10,000번 실행되서 컴파일될 코드라면 더 줄여도 괜찮을 것으로 보임.
이 부분을 좀 많이 낮춰보면 좋을 것 같음(`XX:ReservedCodeCacheSize`도 같이 고려할 필요도 있어보임).

Java 8

  • java 8 기존 default code cache: 24mb, max 240mb

Java 11

  • java 11 기준 code heap 사이즈를 합쳐보면 240mb 정도, max 2gb

  • ReservedCodeCacheSize

    • default size: 240MB

      • tiered compilation 옵션을 disable하면 default size: 48MB (-XX:-TieredCompilation)

    • limit: 2GB

    • 최대 값은 초기 코드캐시 값보다 작아서는 안됨 (-XX:InitialCodeCacheSize)

  • https://docs.oracle.com/en/java/javase/11/tools/java.html

  • SegmentedCodeCache

    • 코드 캐시를 특정 타입의 컴파일된 코드를 포함하는 별도의 세그먼트로 나누는 옵션.

    • non-method, profiled, non-profiled 코드 분리

    • 9부터 들어간 옵션(https://openjdk.org/jeps/197)

Java 17

Table 1. Summary
Option Default Description

InitialCodeCacheSize

플랫폼에 따라 다름

초기 코드 캐시 사이즈

ReservedCodeCacheSize

240 MB

예약된 코드 캐시 사이즈(최댓값)

  • ReservedCodeCacheSize

    • default size: 240MB

      • tiered compilation 옵션을 disable하면 default size: 48MB (-XX:-TieredCompilation)

    • limit: 2GB

    • 최대 값은 초기 코드캐시 값보다 작아서는 안됨 (-XX:InitialCodeCacheSize)

Tiered Compilation

  • java 11 기준으로 CodeHeap(Code Cache)가 3가지 공간으로 나뉨.

    • 이것이 Tiered Compiliation 옵션이 생겨서임

    • 이를 끄고 싶다면 -XX:-TieredComilation 옵션 추가

  • method 종류에 따라 다르게 컴파일하는 옵션

References