[WS/1주차] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가

자바 스터디 https://github.com/whiteship/live-study/issues/1
목표 : 자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.

이것이 자바다 책을 보며 같이 공부했습니다.

1. JVM이란 무엇인가

  • 자바는 기계어가 아닌 중간 단계의 바이트 코드이다. 운영체제가 이를 해석하고 실행하기 위해 실행하기 위해서 필요한 가상 운영체제가 JVM(Java Virtual Machine)이다.
  • 운영체제와 자바 사이 중간에 JVM이 있어 운영체제와 상관없이 자바 프로그램을 개발할 수 있다.
  • 바이트코드는 JVM에서 동일한 실행 결과를 내지만, JVM은 운영체제에 종속적이다. 운영체제에 맞는 JVM을 설치해야 한다.
  • JDK나 JRE를 운영체제에 맞게 설치하면 자동으로 JVM 설치된다.
  • .java 파일 작성(소스 파일) -> 컴파일javac.exe로 컴파일 -> 바이트코드 파일 생성.class -> JVM 구동 명령어java.exe에 의해 JVM에서 해석, 운영체제에 맞는 기계어로 번역된다. 즉 바이트코드는 하나지만 번역되는 기계어가 JVM으로 운영체제에 따라 달라진다.

    01

  • 이렇게 한 번의 컴파일링이 아닌 JVM을 거쳐야 기계어로 번역되기 때문에 기계어(C, C++) 보다 속도가 느리다. 하지만 기계어로 빠르게 변환해주는 JVM 내부에 최적화 된 JIT 컴파일러를 통해 속도 개선된다.

2. 컴파일 하는 방법

  • 자바 소스 파일 작성 : .java인 텍스트 파일을 생성 후 프로그램 소스를 작성한다.
  • 컴파일러javac.exe로 실행시키면 .class 바이트 코드가 생성 된다.
  • 명령 프롬프트에서 Hello.java 파일을 다음과 같이 컴파일 시켜 Hello.class를 생성할 수 있다.

    javac Hello.java
    

3. 실행하는 방법

  • .class 바이트 코드를 실행시키기 위해선 JVM을 통해 기계어로 변환해야 한다. JVM을 구동시키는 명령어는 .java.exe 이다.
  • 명령 프롬프트에서 Hello.class 바이트코드 파일을 다음과 같이 JVM으로 실행시킬 수 있다. 단, java.exe로 바이트 코드를 실행할 땐 .class 확장명을 제외한 이름을 입력해야 한다.

    java Hello
    
  • java.exe 로 명령어를 실행하면 JVM은 바이트 코드 파일을 메모리에 로드하고, 운영체제에 맞는 기계어로 번역한다. 그리고 main() 메소드를 찾아 실행시킨다.

    02

  • java.exe로 JVM을 구동시킬 때, main()은 가장 먼저 찾아 실행시키는 메소드이기에 프로그램 실행 진입점(entry point)라고 한다. main() 메소드가 없으면 클래스를 실행시킬 수 없다.

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

  • 자바 바이트코드란 자바 가상 머신(JVM)이 인식해 실행할 수 있는 명령어 소스코드이다. 변환되는 코드의 크기가 1바이트기 때문에 바이트코드라고 불린다.
  • .class 파일명을 사용한다.

5. JIT 컴파일러란 무엇이며 어떻게 동작하는지

  • Just In Time(실시간)의 약어. JVM 내부에 존재하며 프로그램을 실시간으로 기계어로 번역하는 컴파일 기법이다.
  • 매번 intepreter로 한 줄씩 해석하면 속도가 느려질 수밖에 없는데, 이런 속도 문제를 JIT 컴파일러로 해결한다. JIT 컴파일러는 처음 실행하며 자주 사용하는 명령어(코드블록, 메소드, 반복문 등)을 캐싱하고, 같은 명령어엔 캐싱한 코드를 사용하여 불필요한 번역 작업을 생략할 수 있다.
  • JVM은 다중 스레드 애플리케이션이므로 바이트 코드를 해석하고 실행하는 JVM 스레드는 JIT 스레드의 영향을 받지 않는다. 즉 JIT 컴파일은 어플리케이션 실행과 따로 실행된다. JIT 컴파일이 완료되면 JVM은 바이트 코드 대신 JIT 컴파일된 코드를 사용하게 전환된다. 이를 OSR(on-stack replacement)라고 한다.
  • 단점은 초기 구동시 바이트코드를 컴파일 하며 시간과 메모리 소비가 많기 때문에, 정적 컴파일보다 실행 속도가 느리다.
  • 참고 : https://julio-falbo.medium.com/understand-jvm-and-jit-compiler-part-1-a94c27d32478

6. JVM 구성 요소

03

참고
https://nesoy.github.io/articles/2020-11/ClassLoader
https://medium.com/lucky-sonnie/java-jvm-%EA%B5%AC%EC%A1%B0-9cee6b99d6fc
https://coding-start.tistory.com/205
https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4

  • Class Loader : 자바 바이트코드를 읽어 메모리에 배치한다. 클래스로더는 다음과 같은 일을 한다.
    • loading : 클래스 파일에서 바이트 코드를 읽고 적절한 바이너리 데이터를 만든 뒤 메소드 영역에 저장한다.
    • linking : JVM에서 사용 가능한 형태인지 검증하고, 해당 Type에 맞는 메모리에 할당해준다.
    • initialization : static 값 초기화 및 변수의 값을 할당한다. superClass 초기화 후 해당 Class의 초기화를 진행한다.
  • Memory(Runtime data areas)

    • stack :

      • java method 내 사용되는 값들(지역변수, 매개변수, 리턴값 등)이 저장되고 메소드가 호출될 때 LIFO로 하나씩 생성되고, 실행이 완료되면 LIFO로 하나씩 지워진다.
      • 각 스레드별로 하나씩 생성되며 스레드가 시작될 때 할당된다. 자바 어플리케이션에서 추가적으로 스레드를 생성하지 않았다면 main 스레드만 존재하므로 JVM 스택은 하나이다. 메소드를 호출할 때마다 프레임을 추가(push)하고 메소드가 종료되면 해당 프레임을 삭제(pop)한다. 예외 발생 시 printStackTrace() 메소드로 보여주는 stack trace 각 라인은 하나의 프레임이다.
      • 프레임 내부엔 로컬 변수 스택이 있고 기본 타입 변수와 참조 타입 변수가 추가(push) 되거나 제거(pop) 된다. 스택에 변수가 생성되는 시점은 변수를 초기화(값을 저장) 했을 때고, 변수는 선언 된 스택에서만 존재하기 때문에 블록을 벗어나면 스택에서 제거된다.
    • heap : new 명령어로 생성된 객체(인스턴스)와 배열이 생성되는 영역이다. 여기에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체 필드에서 참조한다. 참조하는 변수나 필드가 없다면 GC(Garbage Collector)를 실행해 쓰레기 객체를 힙 영역에서 자동으로 제거한다. 모든 스레드가 공유한다.
    • method : 클래스(~.class) 파일을 클래스 로더로 읽어 클래스별로 런타임 상수풀(runtime constatnt pool), 필드(field) 데이터, 메소드(method) 데이터, 메소드 코드, 생성자 코드 등을 분류해서 저장한다. JVM이 시작할 때 생성되며 모든 스레드가 공유한다.
    • PC registers : 현재 수행 중인 JVM 명령 주소값이 저장된다. (각 Thread별로 하나씩 생성된다.)
    • native method stack : 다른 언어(C/C++)의 메소드 호출을 위해 할당되는 메모리 구역이다. 언어에 맞게 stack이 생성된다.
  • Execution Engine : Class loader에 의해 메모리에 배치된 바이트코드를 기계어로 바꿔 명령어 단위로 읽어 실행한다.
    • Interpreter : 바이트 코드를 한 줄씩 실행한다.
    • JIT Compiler : Interpereter의 속도 느림을 보완해준다. 반복되는 코드를 발견시 JIT Compiler로 네이티브 코드로 바꿔 인터프리터가 네이티브 코드를 바로 사용해 속도를 빨라지게 해준다.
    • GC : heap 영역 메모리 관리를 해준다. 더 이상 사용하지 않는 객체 메모리를 정리한다.
  • Native Method Interface(JNI) : 네이티브 응용 프로그램(하드웨어와 운영 체제 플랫폼에 종속된 프로그램들), 자바 외 언어(C, C++)로 작성된 라이브러리들을 호출하거나 호출되는 걸 가능하게 하는 프레임워크.
  • Native Method Libraries : 자바 외 언어(C, C++)로 작성된 라이브러리

7. JDK와 JRE의 차이

  • JDK(Java Development Kit, 자바 개발키트)
    • JVM, 라이브러리 API, 컴파일러 등의 개발기구 포함
    • JDK = JRE + 개발에 필요한 도구
    • JVM 내부에도 JRE가 있어 자바 프로그램 개발과 실행이 가능하나, 웹 브라우저에서 실행하는 애플릿(Applet)을 위해선 JRE도 필요하다. 기본적으로 JDK를 설치하면 JRE도 경로에 함께 설치된다.
    • bin 디렉토리 : javac.exe, JVM, java.exe가 포함되어 있다. 개발에 자주 쓰이기 때문에 Path 환경 변수에 bin 위치를 등록하는 게 좋다.
  • JRE(Java Runtime Environment, 자바 실행환경)
    • JVM, 라이브러리 API만 포함
    • JRE = JVM + 표준 클래스 라이브러리
    • 개발된 프로그램 실행만 필요하다면 JRE만 설치

태그:

카테고리:

업데이트:

댓글남기기