Java 최적화 - Chapter9

이유진·2024년 3월 3일
post-thumbnail

JVM의 코드 실행

JVM은 메모리 관리, 사용하기 쉬운 Application 코드 실행 컨테이너를 제공한다.

JVM은 인터프리터로 java bytecode를 실행하고, 자주 사용되는 "핫스팟"을 식별하여 기계어로 compile 하여 실행한다.

Bytecode 해석

JVM 인터프리터는 "스택 머신" 처럼 작동한다
JVM은 다음 세 공간에 주로 데이터를 담는다

  • 평가스택: 메서드별로 하나씩 생성된다
  • 로컬 변수: 결과를 임시 저장한다(특정 메서드별로 존재한다)
  • 객체 힙: 메서드끼리, 스레드끼리 공유된다

JVM 바이트코드 개요

옵코드

  • 스택 머신 작업 코드
  • 1byte로 나타낸다
    • 따라서 옵코드는 0 ~ 255까지 지정가능하고, 약 200개를 사용하고 있다(Java 10 버전 기준)
  • 그림의 iadd 등이 옵코드 이다

    (출처: https://www.mdpi.com/2076-3417/13/17/9580)
  • 여러가지 옵코드에 대한 설명은 책 p.258 ~ 264 참고)

인터프리터

https://github.com/kittylyst/ocelotvm/blob/master/src/main/java/ocelot/InterpMain.java
➡️ 요 파일을 보면 각 옵코드 별 분기코드 로직이 있다

핫스팟

핫스팟은 템플릿 인터프리터라서 시작할 때 마다 동적으로 인터프리터를 구축한다
➡️ 인터프리터 소스코드 분석이 어렵다

  • 핫스팟 전용 바이트코드까지 정의하여 쓴다
  • 특정 옵코드의 일반적인 유스케이스와 hot하게(application에서 빈번하게 호출되어 사용되는) 쓰이는 경우를 차별화 한다

AOT vs JIT

AOT Compile

  • 프로그램 소스 코드를 외부 프로그램에 넣고 바로 실행가능한 기계어를 뽑아내는 과정 (AOT는 소스코드 ➡️ 기계어 변환이 바로 생성된다)
  • 대부분의 실행코드는 어느 플랫폼에서 실행될지 모르므로 CPU 기능을 최대한 활용하지 못한다
  • 하지만, 최적화 결정을 내리는데 런타임 정보가 반영되지 않는다
  • 컴파일 도중 프로세서 특징을 타깃으로 정하면 해당 프로세서에만 사용가능한 실행코드가 만들어진다
  • 따라서 확장성이 떨어진다

JIT Compile

  • 런타임에 프로그램을 기계어로 변환하는 기법
  • 런타임 실행 정보를 수집해서 어느 부분이 자주 쓰이고, 어느 부분을 최적화 해야 가장 효과가 좋은지 프로파일을 만들어 결정을 내린다 (프로파일 기반 최적화)
  • 핫스팟은 프로파일링 정보를 보관하지 않고 VM이 꺼지면 일체 폐기한다. 따라서 항상 프로파일은 처음부터 다시 만들어진다

핫스팟 JIT 기초

  • 핫스팟의 기본 컴파일 단위는 전체메서드
  • 한 메서드에 해당하는 바이트코드는 한꺼번에 네이티브 코드로 컴파일 된다

OSR(온-스택 치환)

프로그램 실행 중에 실행 중인 함수의 최적화된 버전으로 교체할 수 있게 해주는 기술입니다.
이 기술은 주로 동적 언어의 실행 시간(runtime) 최적화에 사용되며, 프로그램의 성능을 향상시키기 위해 사용됩니다.

핫스팟은?

  • 멀티스레드 C++ 애플리케이션이다
  • 실행중인 모든 Java 애플리케이션은 OS관점에서 실제로 한 멀티스레드 애플리케이션 일부일 뿐이다
  • 컴파일 대상으로 낙점된 메서드는 컴파일러 스레드에 올려놓고 백그라운드에서 컴파일한다

vtable

  • 가상 메소드 테이블이다
  • 객체의 메소드 호출이 발생할 때, 실제 호출할 메소드의 주소를 찾기 위해 사용되는 가상 메소드 테이블이다

최적화된 기계어가 생성되면, 해당 klass의 vtable은 새로 컴파일된 코드를 가리키도록 수정된다
➡️ vtable 포인터를 업데이트 하는 작업을 포인터 스위즐링 이라고 한다

핫스팟 내부 컴파일러

C1, C2 두 JIT 컴파일러가 있다.
각각 클라이언트 컴파일러, 서버 컴파일러라고 부르기도 한다.
요즘은 구분하지 않고 새로운 화경에 맞게 최대한 성능을 발휘하도록 변환했다

핫스팟 단계별 컴파일

Java6부터 JVM은 단계별 컴파일 모드를 지원한다

  • 레벨0: 인터프리터
  • 레벨1: C1 - 풀 최적화
  • 레벨2: C1 - 호출 카운터, 백엣지 카운터
  • 레벨3: C1- 풀 프로파일링
  • 레벨4: C2

해당 레벨을 차례로 거치는 것은 아니고, 컴파일 방식마다 경로가 다르다

  • 0 - 3- 4
  • 0 - 2 - 3 - 4
  • 0 - 3 - 1
  • 0 - 4

코드 캐시

  • JIT 컴파일된 코드는 코드 캐시 라는 메모리 영역에 저장된다
  • VM 시작 시 코드 캐시는 설정된 값으로 최대 크기가 고정되므로 확장이 불가하다
  • 코드 캐시가 꽉 차면 그때부터 더이상 JIT 컴파일은 안된다
  • 코드 캐시는 미할당 영역과 프리 블록 연결 리스트를 담은 힙으로 구현된다
  • 네이티브 코드가 제가될 때마다 해당 블록이 프리 리스트에 추가되고, 블록 재활용은 스위퍼 프로세스가 담당한다
profile
BackEnd Developer

0개의 댓글