Skip to main content

2. JVM 이야기

인터프리팅과 클래스로딩#

자바 가상 머신을 규정한 명세서에 따르면 JVM은 스택 기반의 해석 머신입니다. 레지스터는 없지만 일부 결과를 실행 스택에 보관하며 이 스택의 맨 위에 쌓인 값을 가져와 계산을 합니다.

JVM 인터프리터의 기본 로직은 평가 스택을 이용해서 중간 값들을 담아두고 가장 마지막에 실행된 명령어와 독립적으로 프로그램을 구성하는 옵코드(opcode, 명령 코드)를 하나씩 순서대로 처리하는 while 루프안의 switch문 입니다.

자바에 특정 명령을 실행시키기 위해서는 자바 classloading 메커니즘이 관여합니다. 자바 프로세스가 새로 초기화되면 사슬처럼 줄지어서 연결된 클래스로더가 차례차례 작동합니다. 자바 8 이전에는 jar 파일에서 가져오지만, 자바 9 이후에는 런타임이 모듈화되고 클래스로딩 개념 자체가 많이 달라졌습니다.

  • 부트 스트랩 클래스로더의 주 임무는 다른 클래스로더가 나머지 시스템에 필요한 클래스를 로드할 수 있게 최소한의 필수 클래스만 로드합니다.
  • 그다음 확장 클래스로더가 생성됩니다. 부트스트랩 클래스로더를 자기 부모로 설정하고 필요할 때 클래스로딩 작업을 부모에게 넘깁니다.
  • 애플리케이션 클래스로더가 생성되고 지정된 클래스패스에 위치한 유저 클래스를 로드합니다.

자바는 프로그램 실행 중 처음 보는 새 클래스를 디펜던시(의존체, dependency)에 로드합니다. 클래스를 찾지 못한 클래스로더는 기본적으로 자신의 부모 클래스로더에게 대신 찾아보는 역할을 넘깁니다. 이렇게 올라가면서 부트스트랩도 룩업에 실패하면 ClassNotFoundException 예외가 발생합니다. 따라서 빌드 프로세스 수립 시 운영 환경과 동일한 클래스패스로 컴파일하는 것이 좋습니다.

보통 환경에서 자바는 클래스를 로드할 때 런타임 환경에서 해당 클래스를 나타내는 Class 객체를 만듭니다. 그런데 똑같은 클래스를 상이한 클래스로더가 두 번 로드할 가능성도 있으므로 주의해야합니다. 한 시스템에서 클래스는 패키지명을 포함한 풀 클래스명과 자신을 로드한 클래스로더, 두 가지 정보로 식별됩니다.


바이트코드 실행#

자바 소스 코드는 실행되기까지 많은 변환 과정을 거칩니다.

  • 자바 컴파일러 javac를 이용해 컴파일합니다.
    • 자바 소스 코드를 바이트코드로 가득찬 .class 파일로 바꿉니다.
    • 이 경우에 최적화를 거의 하지 않습니다.
    • 바이트 코드는 특정 컴퓨터 아키텍처에 특정하지 않은 중간표현형(Intermediate Representation)입니다.
    • 컴퓨터 아키텍처의 지배를 받지 않으므로 이식성이 좋으며, 자바 언어에 대해 추상화되어 있습니다.
  • JVM은 클래스를 로드할 때 올바른 형식을 준수하고 있는지 빠짐없이 검사합니다.
    • 이러한 테스트 중, 호환되지 않는 버전의 클래스 파일을 만나면 런타임시 UnsupportedClassVersionError 예외가 발생합니다.

클래스 파일 해부도는 아래와 같습니다.

컴포넌트설명
매직 넘버0xCAFEBABE
클래스 파일 포맷 버전클래스 파일의 메이저/마이너 버전
상수 풀클래스 상수들이 모여 있는 위치
액세스 플래그추상 클래스, 정적 클래스 등 클래스 종류를 표시
this 클래스현재 클래스명
슈퍼클래스슈퍼클래스명
인터페이스클래스가 구현한 모든 인터페이스
필드클래스에 들어 있는 모든 필드
메서드클래스에 들어 있는 모든 메서드
속성클래스가 지닌 모든 속성

모든 클래스 파일은 매직 넘버로 시작합니다. (4바이트 16진수) 그다음 4바이트는 클래스 파일을 컴파일할 때 꼭 필요한 메이저/마이너 버전 숫자.

액세스 플래그는 클래스에 적용한 수정자를 결정합니다. 플래그의 첫부분은 일반 프로퍼티로 public 클래스인지, 그 다음은 상속이 금지된 final 클래스인지를 나타냅니다. 또 이 클래스파일이 인터페이스인지 추상 클래스인지, 플래그 끝부분은 클래스 파일이 소스 코드에 없는 합성 클래스인지, annotation인지, enum인지 나타냅니다.

this 클래스, 슈퍼클래스, 인터페이스 엔트리는 클래슺에 포함된 타입 계층을 나타내며 각각 상수 풀을 가리키는 인덱스로 표시합니다. 필드와 메서드는 시그니처 비슷한 구조를 정의하고 여기에 수정자도 포함됩니다. 속성 세트는 더 복잡하고 크기가 고정되지 않은 구조를 나타내는데 쓰입니다.

My Very Cute Animal Turns Savage In Full Moon Areas 로 외우면 좋습니다.

  • Magic, Version, Constant, Access, This, Super, Interfaces, Fields, Methods, Attributes

핫스팟 입문#

자바의 요체인 핫스팟 가상 머신은 성능 관점에서 자바에 큰 변화를 가져왔습니다.

image

자바는 핫스팟을 통해서 프로그램의 런타임 동작을 분석하고 성능에 가장 유리한 방향으로 영리한 최적화를 적용하는 가상 머신입니다. 핫스팟 VM의 목표는 개발자가 억지로 VM 틀에 맞게 프로그램을 욱여넣는 대신, 자연스럽게 자바 코드를 작성하고 바람직한 설계 원리를 따르게 합니다.

JIT 컴파일이란#

자바 프로그램은 바이트코드 인터프리턱가 가상화한 스택 머신에서 명령어를 실행하면서 시작합니다.

이를 위해서 핫스팟은 프로그램 단위(메서드와 루프)를 인터프리티드 바이트코드에서 네이티브 코드로 컴파일 합니다. 이가 바로 JIT(Just In Time) 컴파일이라고 하는 기술입니다.

핫스팟은 인터프리티드 모드로 실행하는 동안 애플리케이션을 모니터링하면서 가장 자주 실행되는 코드 파트를 발견해서 JIT 컴파일을 수행합니다. 이렇게 분석을 통해 미리 프로그래밍한 추적 정보를 취합해서 더 정교하게 최적화를 할 수 있습니다.

JIT 방식으로 컴파일하면 여러모로 이점이 많습니다.

  • 컴파일러가 해석 단계에서 수집한 추적 정보를 근거로 최적화를 결정합니다.
  • 상황별로 수집한 다양한 정보를 토대로 핫스팟이 올바른 방황으로 최적화합니다.

자바처럼 프로필 기반 최적화(PGO, Profile-guided optimization)을 응용하는 환경에서 대부분의 AOT 컴파일러(Ahead-Of Time Compile) 플랫폼에서 불가능한 방식으로 런타임 정보를 활용할 여지가 있으며, 동적 인라이닝(dynamic inlining) 또는 가상 호출(virtual call) 등으로 성능을 개선할 수 있습니다. 핫스팟 VM은 시동시 CPU 타입을 정확하게 감지해 가능하면 특정 프로세서의 기능에 맞게 최적화를 적용가능합니다.

JVM 인트린직(intrinsics, 내장 함수)

  • 프로세서 기능을 정밀하게 감지하는 기법

JVM 메모리 관리#

C, C++ 는 개발자가 메모리 할당/해제 작업을 수행해야합니다. 이에 따라 확정적인 성능과 리소스 관련 작업을 결부시킬 수 있는 장점이 있으나 막대한 책임이 수반됩니다.

자바는 가비지 수집(Garbage Collection) 이라는 프로세스를 이용해 힙 메모리를 자동 관리하는 방식으로 해결합니다. 즉, 가비지 수집은 JVM이 더 많은 메모리를 할당해야할 때 불필요한 메모리를 회수하거나 재사용하는 불확정적인 프로세스입니다.


스레딩과 자바 메모리 모델(JMM)#

자바는 멀티스레드 프로그래밍을 기본 지원합니다. 그렇기에 자바 프로그램이 작동하는 방식이 복잡하고 성능 분석이 어렵습니다. 주로 JVM 구현체에서 자바 애플리케이션 스레드는 각각 정확히 하나의 전용 OS 스레드에 대응됩니다.

자바 멀티스레드 방식은 세 가지 설계 원칙에 기반합니다.

  • 자바 프로세스의 모든 스레드는 가비지가 수집되는 하나의 공용 힙을 가집니다.
  • 한 스레드가 생성한 객체는 그 객체를 참조하는 다른 스레드가 액세스할 수 있습니다.
  • 기본적으로 객체는 변경가능합니다. 즉, 객체 필드에 할당된 값은 프로그래머가 애써 final 키워드로 불변을 표시하지 않는 한 바뀔 수 있습니다.

JVM 구현체 종류#

오라클이 제작한 핫스팟 이외에도 제각기 다른 방법으로 구현한 자바 구현체가 많습니다.

  • OpenJDK
    • 자바 기준 구현체(reference implementation)를 제공하는 특별한 오픈소스 프로젝트
  • Oracle
    • 가장 잘알려진 구현체
    • 오라클 상용 라이선스
  • Zulu
    • Azul 시스템이 제작한 무료 OpenJDK
  • IcedTea
  • Zing
  • J9
  • Avian
  • Android
    • 최초의 안드로이드는 여러가지 자바 클래스 라이브러리 구현체와 교차 컴파일러를 사용했음.

JVM 라이선스#

JVM 구현체는 거의 다 오픈 소스입니다. 그러나 오라클 자바(자바 9 이후) 라이선스 체계는 좀 복잡합니다. 다만, 이를 일종의 처리를 했기 때문에 거의 큰 차이가 사라집니다.

즉, 오라클 JDK와 OpenJDK는 라이언스 외에는 아무런 차이가 없습니다. 다만 오라클 라이선스는 신경써야하는 조건이 있습니다.

  • 회사 밖으로 오라클 바이너리를(ex. 도커 이미지) 재배포 행위는 허용되지 않습니다.
  • 사전 동의(서비스 지원 계약) 없이 오라클 바이너리를 함부로 패치하면 안됩니다.

즉, JVM을 신중하게 고려해야합니다.


JVM 모니터링과 툴링#

JVM은 성숙한 실행 플랫폼이며, 실행 중인 애플리케이션의 instrumentation, 모니터링, 관측하는 다양한 기술을 제공합니다.

  • 자바 관리 확장(JMX, Java Management Extensions)
    • JVM과 그위의 동작하는 애플리케이션을 제어하고 모니터링하는 툴
    • JVM을 관리하는 기본 수단
  • 자바 에이전트(Java Agent)
    • 자바 언어로 작성된 툴 컴포넌트
  • JVM 툴 인터페이스(JVMTI, JVM Tool Interface)
    • JVM의 네이티브 인터페이스
    • JVM 이벤트를 모니터링하며 알림을 받을 수 있도록 만든 통신 인터페이스
    • 일반적으로는 사용을 안하는 것이 좋습니다. (위험합니다.)
  • 서비서빌리티 에이전트(SA, Serviceability Agent)
    • 자바 객체, 핫스팟 자료 구조 모두 표출 가능한 APi와 툴을 모아놓은 것
    • JVM에서 코드를 실행할 필요없습니다.

VisualVM#

VisualVM은 일종의 시각화 툴입니다. JVM 어태치 메커니즘(attach mechanism, 자바 Attach API를 이용해 애플리케이션을 확인합니다.)을 이용해 실행 프로세스를 실시간 모니터링을 합니다. 일반적으로 다섯 가지의 탭을 기본 제공합니다.

  • 개요(Overview)
    • 자바 프로세스에 관한 요약 정보
  • 모니터(Monitor)
    • CPU, 힙 사용량 등 JVM을 고수전에서 원격 측정한 값입니다.
    • 로드/언로드된 클래스 개수 및 실행 중인 스레드 개수 현황 등
  • 스레드(Thread)
    • 애플리케이션 각 스레드가 시간대 별로 표시됩니다.
  • 샘플러 및 프로파일러(Sample and Profile)
    • CPU 및 메모리 사용률에 관한 단순 샘플링 결과가 표시됩니다.

설치시 참고, Intellij에 설치

Last updated on