Posts JVM
Post
Cancel

JVM

JVM


JVM이란

JAVA Virtial Machine의 약자로 Java Byte Code를 OS에 맞게 해석해주는 역할을 하는 가상머신

  • Java Compiler는 .java파일을 .class라는 Java byte code로 변환
    • Byte 코드는 기계어가 아니기 때문에 OS에서 바로 실행이 안됨
  • JVM은 OS가 ByteCode를 이해할 수 있도록 해석해 주는 역할을 함
    • 따라서 JVM은 c언어 같은 네이티브 언어에 비해 속도가 느렸지만 JIT(Just In Time)컴파일러 구현을 통해이점을 극복
  • Byte코드는 JVM위에서 OS상관없이 실행된다.
    • JVM은 OS에 독립적이지만 의존적이다.

JVM의 구성

image

  • Class Loader
    • Runtime 시점에 .class에서 바이트코드를 읽고 메모리에 저장
    • 로딩: 클래스를 읽어오는 과정
    • 링크: 레퍼런스를 연결하는 과정
    • 초기화: static 값들을 초기화 및 변수에 할당
  • Runtime Data Areas
    • Heap 과 Method는 모든 쓰레드가 공유 나머지는 쓰레드 마다 생성
    • JVM이 프로그램을 수행하기 위해 OS로 부터 별도로 할당받은 메모리 공간
      • PC Register: CPU가 Instruction을 수행하는 동한 필요한 정보를 저장
      • JVM Stack: Thread가 시작될 때 생성되며 Method와 Method 정보 저장
      • Navtive Method Stack: Java 이외의 언어로 작성된 native 코드를 위한 Stack(JNI)
      • Method Area: 모든 쓰레드가 공유하는 메모리 영역(클래스, 인터페이스, 메소드, 필드, Static 변수등의 바이트 코드 등을 보관)
      • Heap: 런타임시 동적으로 할당하여 사용하는 영역 class를 통해 instance를 생성하면 Heap에 저장됨
        • Heap의 경우 명시적으로 만든 class와 암묵적인 static 클래스(.class 파일의 class)가 담긴다.
        • 또한 암묵적인 static 클래스의 경우 클래스 로딩 시 class 타입의 인스턴스를 만들어 힙에 저장한다. 이는 Reflection에 등장한다.
  • Execution Engine
    • Load된 Class의 ByteCode를 실행하는 Runtime Module
    • Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트 코드는 Execution Engine에 의해 실행(바이트 코드를 명령어 단위로 읽어서 실행)

Class Loader

imageClass Loader

  • 로딩, 링크, 초기화 순으로 진행된다.
  • 로딩
    • 클래스 로더가 .class 파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들고 “메소드” 영역에 저장
    • 이때 메소드 영역에 저장하는 데이터
      • FQCN(Fully Qualified Class Name)
      • 클래스인터페이스이늄 인지를 저장
      • 메서드와 변수
    • 로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여 “힙” 영역에 저장
  • 링크
    • Verify, Prepare, Resolve(Optional) 세 단계로 나눠져 있다.
    • 검증: .class 파일 형식이 유효한지 체크한다.
    • Preparation: 클래스 변수(static 변수)의 기본값에 따라 필요한 메모리
    • Resolve: 심볼릭 메모리 레퍼런스를 메서드 영역에 있는 실제 레퍼런스로 교체한다.
  • 초기화
    • Static 변수의 값을 할당한다.(static 블럭이 있다면 이때 실행된다.)
  • 클래스 로더는 계층 구조로 이뤄져 있으면 기본적으로 세가지 클래스 로더가 제공된다.
    • 부트 스트랩: JAVA_HOME/lib에 있는 코어 자바 API를 제공한다. 최상위 우선순위를 가진 클래스 로더
    • 플랫폼: JAVA_HOME/lib/ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는다.
    • 애플리케이션: 앱 ClassPath(앱을 실행 할 때 주는 -classpath 옵션 또는 java.class.path 환경변수의 값에 해당하는 위치)에서 클래스를 읽는다.

Java Compiler

흔히 우리가 JIT Compiler라고 부르는 Compiler는 실행중에 바이트 코드를 여러가지 다양한 테크닉을 사영하여 JVM 해석 엔진 없이 바로 수행되는 기계어 코드를 만들어 낸다. 그럼으로서 바이트코드가 가지는 장점과 기계어가 가지는 장점을 결합할 수 있다.

과정

  1. Java Compiler(javac 명령어 실행)에 의해 Java Source(.java 확장자)로부터 Byte Code(.class 확장자)가 생성된다.
  2. JVM에 있는 Class Loader에 의해 Byte Code는 JVM내로 로드되고 실행엔진에 의해 기계어로 해석되어 메모리 상(Runtime Data Area)에 배치된다.
  3. 실행엔진에는 Interpreter와 JIT(Just-In-Time) Compiler가 있는데, Interpreter에 의해 Byte Code를 한 줄씩 읽어 실행하다가 적절한 시점에 Byte Code 전체를 컴파일하고 더이상 인터프리팅하지 않고 해당 코드를 직접 실행한다.
  • JIT Compiler에 의해 해석된 코드는 캐시에 보관하기 때문에 한 번 컴파일된 후에는 빠르게 수행할 수 있다는 장점이 있습니다. 하지만 코드 전체를 컴파일하기 때문에 인터프리팅하는 것보다 시간이 오래 걸리므로 한 번만 실행해도 되는 코드에 대해서는 인터프리팅하는 것이 유리합니다.

  • Interpreter : 자바 Byte Code를 한 줄씩 실행. 전체 성능면에서 불리.

  • JIT Compiler : 전체 Byte Code를 컴파일하고 캐시에 보관해놓고 직접 실행. 한 번만 실행해도 되는 코드에 대해서는 Interpreter가 유리.

장점

  • 생성되는 코드의 안정성
    • Java가 수행중 만들어내는 기계어 코드는 안전한 공간(sandBox)안에서 돌아가기 때문에 외부 해킹에 안전
  • 동작하는 메모리 공간의 안전성
    • 모든 자바 객체들은 Heap이라는 독립적인 공간에서만 수행
    • 다른 Process 와 다른 메모리 공간을 사용하기 때문에 Stack overflow에 강함
  • 최적화 재사용에 유일한 관련 클래스간 상속구조
    • 메모리 위치상 가깝게 관련된 객체와 메소드들을 위치시킨다.
    • method inlining 같은 성능을 높이기 위한 테크닉들이 자바에서 효율적으로 작동
  • 동적 최적화와 그것에 대한 취소, 재 최적화 가능
    • static 언어와 다르게 dynamic class loading으로 어떤 방식으로든 수행중 변경 가능 compiler를 통한 최적화가 수시로 이루어 진다.

Compiler 기술들

  • Hot Spot Detection
    • JVM이 ByteCode를 해석하다가 루프등을 만나 몇번이나 중복적인 해석이 이루어진다고 판단되면 Byte코드를 기계어로 직접 컴파일하는 방식
    • 기존의 모든 기본 코드를 수행전에 컴파일 하는 방식은 수행 자체는 빠르지만 프로그램 크기가 커지고 기기별 이식성이 떨어지기도 한다.
  • Method inlining
    • 클래스 안에서 사용된 다른 클래스에 대해 method inlining을 수행함으로서 다른 메모리 공간에 있는 메소드에 대해 호출하는 것을 피할 수 있다.
    • 이걸 취소할 수도 있다.
  • reflection
    • 객체를 명시적으로 코드에서 new하지 않아도 임의의 객체를 동적으로 생성하고 메소드를 호출할수 있는 reflection은 자바 동적 클래스로딩의 핵심

JIT

핫스팟 JVM의 핵심 컴파일 방법중 하나로 먼저 인터프리터가 동작하여 코드를 실행

일정시간 동안 인터프리터가 코드를 해석하며 컴파일하기에도 충분할 정도로 자주 호출되는 메소드가 무엇인지 알아내고 해당 메소드만 컴파일

위의 컴파일 장점이 되는 모든 기능들은 자바 1.6 이후의 HotSpot 엔진의 성능향상의 덕이다.

HotSpot이란 Sun Microsystem사의 Java 엔진 이름이다. Java VM의 엔진은 다양하지만 크게 HotSpot(Sun/Oracle), J9(IBM), JRocket(Oracle) 정도가 기업용자바 환경에서 주로 사용되는 것들이다. JRocket은 서버쪽 성능개선에 집중된 것으로 HostSpot에 그 핵심기능이 옮겨져있기에 현재 대새는 Oracle Hotspot이다. HotSpot은 오픈소스 자바 프로젝트인 OpenJDK의 JVM엔진이기도 하다.

–Tips–

interpreter가 수행된다음에는 프로그램에 대해 더욱 많은 정보를 저장하면서 분석할수 있다는 점이다. 어느 지점이 hot spot인지도 monitor링 될 뿐만아니라 어느 함수가 어느 함수를 부르고 있는지도 명확하게 파악될수 있다. 자바 기반의 어플리 케이션이 디버깅에 매우 유리한 이유이기도 하다.

Client Compiler

클라이언트 모드에서 동작하는 컴파일러는 주로 프로그램의 시작시간을 최소화하는데에 집중한다.

  • 클라언트 모드 총 세단계
    1. 바이트코드를 해석해서 최적화를 쉽게 하기 위해, HIR이라고 하는 정적인 바이트코드 표현을 만듬
    2. HIR로부터 플랫폼에 종속적인 중간표현식 (LIR) 을 만듬
    3. LIR을 사용해 기계어 생성
  • 클라이언트 모드 JIT의 특징은 바이트코드로부터 최대한 많은 정보를 뽑아내어 실제 동작하는 코드 블럭에 대해 최적화를 집중하는 것이다.
  • 전체적인 최적화에는 큰 관심이 없다.

Server Compiler

서버모드의 Jit compiler는 부분적인 코드 실행보다 전체적인 성능 최적화에 관점을 둔다.

  1. 일반적인 컴파일러 최적화 기술들을 이용해 일단 코드들을 최적화 한다.
    • 죽은 코드 삭제(Dead Code Elimination), loop 변수의 끌어올리기(Loop invariants hoisting), 공통 부분식 제거(Common Subexpression Elimination), 상수 지연(Constant propagation), 전역 코드 이동(Global Code motion) 등
  2. 자바에 최적화된 최적화를 수행한다.
    • Null Check 삭제, 배열의 Range Check 삭제, 예외처리 경로 최적화.
    • 대단위 RICS 레지스터들을 최대한 활용하기 위한, Graph연산을 통한 register할당
    • 이런 과정을 통해 상대적으로 느린 속도로 JIT이 수행된다. 하지만 코드의 수행은 더욱 빠르다.
This post is licensed under CC BY 4.0 by the author.