ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JVM은 무엇이며, 자바 코드는 어떻게 실행하는 것인가?
    Develop/Java 2021. 1. 11. 10:13

     

    - JVM이란 무엇인가?
    - 컴파일 하는 방법
    - 실행하는 방법
    - 바이트코드란 무엇인가
    - JIT 컴파일러란 무엇이며 어떻게 동작하는가
    - JVM 구성 요소
    - JDK와 JRE의 차이

     

    1. JVM이란 무엇인가?

    Java Virtual Machine , 즉 자바를 실행하기 위한 가상 기계. SW로 구현된 HW임. Java Application은 JVM에서만 실행된다. 그러므로 반드시 실행을 위해서 JVM이 필요하다.

    특징

    • Interpret : 실행시에 해석된다.
    • JIT Compiler : byte code를 HW의 기계어로 바로 변환해주는 역할이다. 인터프리터 뿐만 아니라, 이를 통해 속도 향상이 일어났다.
    • OS와 하드웨어에 독립적 : JAVA Application은 JVM과 상호작용을 한다.
    • Garbage Collection : 클래스 인스턴스는 사용자 코드에 의해 명시적으로 실행되고, GC에 의해 자동으로 파괴된다.

     

    2. 컴파일 하는 방법 / 실행하는 방법

    Java Version은 openjdk 1.8

    java study에 main.java file 생성

    java file을 명령어 javac을 통해 compile 가능하다.

    > javac CompileTest.java

     

    이후 directory에서 ll을 입력하면 compileTest.class가 생성된것을 볼 수 있다. compile된 class file을 실행시키기 위한 명령어

    > java CompileTest 를 사용

     

     

    동작원리

    만약 .class file을 실행하는 경우

    1. .class file에서 클래스파일의 이름과 같은 클래스를 찾는다.

    2. 그 클래스 내에서 main() 메소드를 찾는다.

    3. main{}내의 코드를 순차적으로 실행하고, 종료하도록 약속되어있다.

     

    public static void main(String[] args) 

    main method의 선언부. 프로그램을 실행할 때 java.exe에 의해 호출될 수 있도록 미리 약속된 부분임. java application은 main method의 호출로 시작해서 처음~ 마지막까지 수행함.

     

    하나의 java application에는 반드시 main method를 포함한 클래스가 존재해야 한다.

    소스파일의 이름은 public class의 이름과 일치해야한다. 소스파일의 이름은 소스파일 내의 어떤 클래스의 이름으로 해도 상관 없다.

     

     

    3. 바이트코드란 무엇인가

    가상신이 인식하기 쉬운 코드 = 바이트 코드.

    java코드가 .class 코드로 컴파일되면서 변환되는 code의 형식이다. 각각의 연산 (op) 코드가 1바이트 길이로 되어있어 byte code라고 불린다. byte code를 이용하여 JIT 컴파일을 하고, 이식성 / 독립성 / 빠른 컴파일 속도를 가지고 있다.

     

    로딩 - 링크 과정을 거쳐야 하지만, 분명히 JVM에서 실행 가능하다. 그래서 꼭 java spec이 아니더라도, JVM 스펙의 class 파일 구조에 맞는 바이트코드를 만들어낼 수 있으면, 어떤 언어든 사용 가능하다.

     

    Class 단위의 constant pool

    소스코드에서 정적으로 파악 가능한 변수, 상수, 메서드 등의 정보가 저장된다.

     

    Byte code (구현부) 

    연산,제어,메소드 호출 등은 JVM 명령어와 상수풀에 저장된 항목을 오퍼랜드로 사용한다. 이것들이 byte code로 변환되고, code 구현부에 저장된다.

     

     

    4. JIT Compiler란 무엇이며 어떻게 동작하는가

     

    컴파일러 vs 인터프리터

    인터프리터는 코드를 한줄 읽고 바로 결과를 출력한다. 같은 기능을 하는 코드가 다시 나와도 다시 해석한다.

    컴파일러는 한번만 소스코드를 컴파일하여, 같은 기능을 하는 코드가 있으면 미리 컴파일된 결과를 이용하여 출력한다.

     

    -> 한번만 실행될 code : Interpreter / 여러번 실행될 code : compiler

     

    핫스팟 (가장 자주 실행되는 영역) JVM

    : 바로 코드를 컴파일하는게 아니라, 일정 시간 인터프리터가 우선 동작하고, 컴파일이 충분하면 해당 메소드만 컴파일한다. 최적화를 수행하는것이다.

     

    Code Cache 

    : JIT는 코드캐시라는 곳에 자주 사용되는, 컴파일된 코드를 저장한다. 컴파일된 코드들은 자주 사용되는 코드들로 동적으로 바뀐다.

     

    Compiler Option

    : client / server에 따라 컴파일 옵션이 나뉜다.

     

    JIT Compiler 

    인터프리터의 단점을 보완하기 위해 도입되었다. 인터프리터 방식으로 실행하다가, 적절한 시점에 byte code전체를 컴파일하여 네이티브 코드로 변경한다. 이후에는 계속 native code로 실행한다. native code는 cache에 보관하기 때문에 한번 컴파일된 코드는 계속 빠르게 수행 가능하다.

     

     

    컴파일을 위해 메서드를 선택하면, JVM은 해당 바이트 코드를 JIT Compiler에 공급한다. JIT는 메서드를 올바르게 컴파일하기 전에, 바이트코드의 구문을 해석한다. JIT 컴파일러가 메서드를 분석하는 것을 돕기 위하여, 바이트코드는 먼저 '트리'로 재작성된다. 이것은 기계 코드에 가까운데, 마지막에는 이 트리가 기본 코드로 변환된다.

     

    컴파일 단계

     

    1. Inlining 

    : 소형 메서드 트리가 해당 호출 트리로 병합되거나, '인라인되는' 프로세스이다. 이렇게 하면, 자주 실행되는 메서드의 호출 속도 향상이 가능하다. 

    2. 로컬 최적화 ( Local Optimizations )

    : 한번에 하나의 작은 코드 섹션을 분석하고 , 개선한다. 여러 로컬 최적화에서, classic static 컴파일러에 사용되는 시험 및, 테스트된 기술을 구현한다.

    3. 제어 흐름 최적화 ( Control Flow Optimizations )

    : 메서드나 메서드의 특정 섹션 내의 제어 플로우를 분석하고, 코드 경로를 재배열해서 효율성을 향상시킨다.

    4. 전역 최적화 ( Global Optimizations ) 

    : 전체 메서드에서 동시에 수행된다. 더 많은 자원을 사용하며, 더 많은 컴파일 시간이 필요하지만, 성능에 크게 향상될 수 있다.

    5. Native Code 생성

    : native code 생성 프로세스는 일반적으로 이 컴파일 단계동안 메서드 트리가 시스템 코드 명령어로 변환되고, 아키텍쳐 특성에 따라 일부 소규모 최적화가 수행된다.

     

     

    실질적으로 여러 JIT 컴파일 메서드는 시스템에서 미사용 처리 코어가 존재하는 경우에만 성능 향상이 존재한다. 미사용 처리 코어가 없는 경우, 컴파일 스레드 수를 늘려도, 성능 향상 가능성이 적다.

     

    ref ) www.ibm.com/support/knowledgecenter/ko/SSYKE2_7.0.0/com.ibm.java.lnx.70.doc/diag/understanding/jit_optimize.html 

    5. JVM 구성 요소

    JVM

    Java byte code를 OS에 특화된 코드로 변환하여 실행한다. 플랫폼 종속적이며, JVM에 의해 java가 플랫폼 독립적이게 되는것이다.

     

    1. Class loader
    2. Execution Engine
    3. Runtime Data Area ( Memory )
     -  Method Area ( Class / Code / Static Area )
     -  Heap
     -  Stack
     -  PC Register
     -  Native Method Stack
    4. Native

     

     

    Class Loader

    클래스 로더는 로딩 -> 링크 -> 초기화를 진행한다. 클래스 파일을 적재하는 역할을 한다.

     

    로딩

    클래스 로더가 class 파일을 읽고 그 내용에 따라 바이너리 data를 만들고, 메서드 영역에 저장한다. 로딩이 끝나면 해당 클래스 타입의 class 객체를 생성해서 heap에 저장한다.

     

    링크

    검증(verify)은 .class 파일 형식이 유효한지 체크하고, preparation은 클래스 변수와 기본값에 필요한 메모리, resolve는 심볼릭 메모리 레퍼런스를 메서드 영역에 있는 실제 레퍼런스로 교체한다.

     

    초기화

    static 변수 값을 할당한다. 이 때 static이 실행되는것.

     

     

    Execution Engine

    class loader에 의해 runtime data area에 적재된 클래스들을 기계어로 변경해서 명령어 단위로 실행한다.

    1. 하나씩 실행하는 interpreter 방식

    2. Byte -> Native로 변환하는 JIT Compiler 방식

    두가지 방식이 존재한다.

     

     

    Runtime Data Area

    • Method Area  
      • 클래스 멤버변수 이름, 데이터 타입, 리턴 타입, 상수 풀, static 변수 등이 저장된다. 해당 정보들은 공유되고, 클래스 수준의 정보를 가지고 있다.
    • Heap Area
      • new 연산자로 생성된 객체, 배열을 저장한다. Event event = new Event();로 생성한 event는 stack에 저장되며, new로 생성한 Event()가 heap에 저장된다. event 변수는 heap 영역의 주소값을 가지고 있는것이다.
    • Stack Area
      • Thread가 생성될때마다 생성된다. 지역 변수가 여기에 저장된다. method  호출이 이루어질 때마다 스택이 개별적으로 생성된다. 동시성 문제 해결을 위해 동기화 블럭을 지정하는데, 이것 없이 해결하려면 지역 변수를 이용한다.
    • PC Register
      • Program Counter - 현재 스레드가 실행되는 부분의 주소와 명령을 저장한다. 이것을 이용해서 multi threading이 가능한것이다. multi thread 환경에서 공유되는 부분은 heap / method 영역이고, stack / PC Register 는 독자적이다.
    • Native Method Stack
      • 자바 외의 언어 코드가 저장되는곳이다.

     

    [ Java SourceFile ] 

    ( Java Compiler ) -> class file 변환

    ( Class loader ) -> Runtime Data Area에 클래스파일 적재

    ( Execution Engine ) -> 자바 메모리에 적재된 클래스들을 기계어로 변환해 명령어 단위로 실행하고, GC는 Heap 영역에 적재된 객체들 사이에서 참조되지 않은 객체를 제거한다.

     

     

     

    6. JDK와 JRE 차이 

     

     

     

     

     

     

    'Develop > Java' 카테고리의 다른 글

    연산자  (0) 2021.06.12
    자바 데이터 타입, 변수 그리고 배열  (0) 2021.03.25
    Java String  (0) 2020.11.13

    댓글

Designed by Tistory.