[Java] JVM이란?

Arthur·2023년 5월 4일
0
post-thumbnail

JVM이란?!


❓JVM 왜 공부해야 할까?

  • 첫 회사에 취업하기 전에는 단순히 면접을 대비하기 위해 공부했었습니다.
    그런데 어떤 주제에 대해 깊게 공부하면 할수록 JVM의 개념을 알아야 이해가 편하다는 것을 알았습니다.
    어떤 주제는 JVM의 개념을 모르면 이해가 거의 안되는 주제도 있었습니다.(ex. Reflection)
    JVM을 전부 이해한다는 것은 힘들겠지만 생각과 의문을 통해 계속 깊게 파고 공부할 예정입니다.
  • JVM이 동작하는 원리가 Java의 특징을 정의하고, 그 특징들이 현대 프로그래밍 언어들에 큰 영향을 주었기 때문입니다.

❓JVM이란?

  • Java Virtual Machine의 약자
  • 프로그램을 실행하기 위해 물리적 머신(즉, 컴퓨터)와 유사한 머신을 소프트웨어로 구현한 것을 말한다고 할 수 있다.
    • VM(가상 머신)이란
      • 물리적 컴퓨터에서 실행하는 모든 소프트웨어를 실행하는 데 사용할 수 있는 “소프트웨어 컴퓨터
      • 물리적 기계와 마찬가지로 가상 머신에는 자체 운영 체제(Windows, Linux 등), 스토리지, 네트워킹, 구성 설정 및 소프트웨어가 포함되어 있으며 해당 호스트에서 실행되는 다른 VM과 완전히 분리된다.
    • 가상 머신을 사용하는 이유
      • 가상화는 여러 작은 워크로드를 하나의 물리적 컴퓨터로 통합하여 효율성을 높이고 IT 비용을 절감합니다.
  • 가상 머신(Virtual Machine)은 시스템 가상 머신(ex. 하이퍼바이저)이 아닌 프로세스 가상 머신을 의미한다.
  • JVM의 역할은 자바 애플리케이션을 클래스 로더(Class Loader)를 통해 읽어 들여서 자바 API와 함께 실행하는 것입니다.

❓JVM의 특징은?

  • 스택 기반의 가상 머신
    • 스택 기반 VM과 레지스터 기반 VM 차이는?
      • ❓ 레지스터란?
        • CPU가 요청을 처리하는 데 필요한 데이터를 일시적으로 저장하는 다목적 공간이다.
        • 공간은 작고 가격은 비싸지만 CPU에 직접 연결되어 있어서 연산 속도가 RAM, HDD, SDD 보다 빠르다.
        • CPU는 자체적으로 데이터를 저장할 수 없으므로 레지스터를 이용하여 연산처리 및 번지 지정을 도와준다.
        • 레지스터 관련 강의 ⇒ 링크
      • 피연산자를 저장하고 다시 가져오는 매커니즘이 다르다.
      • 잘 정리된 블로그 ⇒ 링크
  • 심볼릭 레퍼런스
  • 가비지 컬렉션
  • 기본 자료형을 명확하게 정의하여 플랫폼 독립성 보장
  • 네트워크 바이트 오더(network byte order)

❓JVM 사용하는 이유는?

  • 플랫폼(OS) 독립적으로 구동 및 개발을 할 수 있습니다.
    • 자바의 WORA(Write Once Run Anywhere) 컨셉이 이에 해당된다.
    • 구동 되는 환경에 구애 받지 않고 실행될 수 있도록 하고 다양한 환경 간 호환성을 유지합니다.
      즉 Java로 작성된 프로그램은 플랫폼에 맞는 JVM만 설치되어 있으면 OS에 영향을 받지 않고 동작이 가능합니다.

컴파일 및 실행하는 방법


❓컴파일러란?

  • 특정 프로그래밍 언어로 쓰여 있는 문서를 다른 프로그래밍 언어로 옮기는 언어 변역 프로그램을 말한다.
  • 고급 프로그래밍 언어를 실행 프로그램으로 만들기 위해 저급 프로그래밍 언어로 바꾸는데 사용된다.
  • 평범한 텍스트인 원시(Raw).java 파일을 받아서 실행 가능한 .class 파일로 만드는 기능이 있는 소프트웨어다.

❓컴파일 하는 방법은?

  1. cmd창에서 echo 명령어를 통해 자바(.java) 파일을 생성한다.

  1. 생성된 파일에 “Hello World”를 출력하는 코드를 작성한다.

  1. cmd창에서 javac 명령어를 활용해 자바(.java) 파일 컴파일 하기

java 명령어를 통해 컴파일된 클래스(.class) 파일을 실행해본다.


❓javac 명령어란?

javac <option> <filename.java>
  • 위와 같이 명령어를 통해 자바(.java) 파일을 컴파일 후 클래스(.class) 파일을 생성한다.
  • 자바 소스 코드를 컴파일 과정을 거쳐, 바이트 코드인 클래스 파일을 만들게 된다.
  • JDK의 개발 도구 중에 javac가 포함되어 있다.

❓클래스(.class) 파일이란?

  • 바이트 코드로 작성된 파일이다.
  • JVM이 설치된 장치에서 java 명령어를 통해 실행이 가능하다.

❓컴파일과 인터프리트의 차이는?

컴파일

  • CPU는 어셈블리나 바이너리 코드라고 불리는 특정 명령어만을 실행시킬 수 있다.
    그 말은 CPU가 실행하는 모든 프로그램은 이 명령어들로 변환이 가능하다.
  • C++와 Fortran과 같은 언어들은 바이너리 (컴파일된) 코드로 실행되어 컴퍼일형 언어라고 불린다.
    즉 프로그램이 작성되고 정적 컴파일러는 바이너리를 생성합니다.
    그 바이너리 내의 어셈블리 코드는 특정 CPU만을 대상으로 합니다.

인터프리트

  • 동일한 프로그램 소스 코드는 머신이 같은 인터프리터라고 불리는 프로그램을 갖고 있으면 어떤 CPU에서든 실행이 가능합니다.
  • 각 라인이 실행되면서 프로그램의 각 코드를 한 줄씩 바이너리 코드로 번역합니다.

인터프리터 언어로 작성된 프로그램은 컴파일 언어보다 이식성이 높습니다.
동일한 코드를 짜서 적절한 인터프리터가 있는 머신에 올리고 실행시킬 수 있습니다.

하지만 컴파일 언어보다 빠르게 실행되지는 않습니다.

반복되는 작업을 하는 일이 있다고 생각해보면 인터프리터는 매번 실행될 때 코드의 각 줄을 다시 번역합니다. 반면에 컴파일된 코드는 되풀이해서 번역할 필요가 없습니다.


바이트코드란?


❓ 자바 바이트 코드(Java byte code)란?

  • 특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행 프로그램을 위한 이진 표현법
  • 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다.
  • 자바 가상 머신(JVM)이 이해할 수 있는 언어로 변환된 자바 소스 코드를 의미한다.
  • 자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불리고 있다.

❓ 바이트 코드(Byte Code)와 기계어(Machine Code)의 차이는?

  • 바이트 코드는 JVM에 의해 기계어로 변환이 되지 않으면 기계(컴퓨터)가 읽을 수 없지만,
    기계어는 변환 없이 기계(컴퓨터)가 CPU에서 처리가 가능합니다.
  • 바이트 코드는 JVM이 설치 되어 있으면 플랫폼에 상관없이 실행이 가능하지만,
    기계어는 각 시스템 아키텍처 및 시스템과 관련된 기본 명령어가 달라서,
    플랫폼에 독립적이지 않습니다.

JIT 컴파일러란 무엇이며 어떻게 동작할까?


❓ JIT 컴파일러란?

  • JIT 컴파일(Just-In_Time compliation) 또는 동적 번역(Dynamic translation)이라고 한다.
  • JIT 컴파일러는 프로그램을 실제 실행하는 시점에 기계어로 변역하는 컴파일러이다.
  • 인터프리터의 단점을 보완하기 위해 도입되었다.
  • 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다.
    • 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 떼문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.
    • 코드 캐시(Code Cache)에 저장 된다고 한다. ⇒ 링크

❓ JIT 컴파일러는 어떻게 동작할까?

  • JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 하나씩 인터프리팅 하는 것보다 훨씬 오래 걸리므로, 만약 한 번만 실행되는 코드라면 컴파일 하지 않고 인터프리팅 하는 것이 훨씬 유리하다.
  • JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.
  • IR(Intermediate Representation) Generator
    • 중간 코드 생성
  • 코드 옵티마이저(Optimizer)
    • 생성된 중간 코드를 최적화 합니다.
  • Code Generator
    • 기계어 혹은 네이티브 코드 생성합니다.
  • Profiler
    • JVM 레벨에서 자바 바이트 코드 구성 및 작업을 모니터링하는 도구이다.

JIT 컴파일러는 바이트 코드를 일단 중간 단계의 표현인 IR(Intermediate Representation)로 변환하여 최적화를 수행하고 그 다음에 네이티브 코드를 생성한다.


JVM의 구성 요소


클래스 로더(Class Loader)가 컴파일된 자바 바이트 코드를 런타임 데이터 영역(Runtime Data Areas)에 로드하고, 실행 엔진(Execution Engine)이 자바 바이트 코드를 실행한다.


❓JVM의 구성요소는?

위 사진을 보면 알 수 있듯이 JVM은 3가지 메인 하위 시스템(Subsystem)으로 나뉘어져 있다.

  1. 클래스 로더(Class Loader)
  2. 런타임 데이터 영역(Runtime Data Area)
  3. 실행 엔진(Execution Engine)

1. 클래스 로더(Class Loader)

컴파일된 자바 바이트 코드를 런타임 데이터 영역(Runtime Data Areas)에 로드하고, 실행 엔진(Execution Engine)이 자바 바이트 코드를 실행한다.

클래스 로더는 런타임 중에 JVM의 메소드 영역에 동적으로 Java 클래스를 로드하는 역할을 한다. 클래스 로더에는 로딩, 링크, 초기화 단계로 나뉘어져 있다.

로딩(Loading)

  • 자바 바이트 코드(.class)를 메소드 영역에 저장한다.
  • 각 자바 바이트 코드(.class)는 JVM에 의해 메소드 영역에 다음 정보들을 저장한다.
    • 로드된 클래스를 비룻한 그의 부모 클래스의 정보
    • 클래스 파일과 Class, Interface, Enum의 관련 여부
    • 변수나 메소드 등의 정보

링크(Linking)

  • 검증 : 읽어 들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사한다.
  • 준비 : 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스를 나타내는 데이터 구조를 준비한다.
  • 분석 : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다.

초기화(Initialization)

  • 클래스 변수들을 적절한 값으로 초기화 한다. 즉, static 필드들이 설정된 값으로 초기화한다.

2. 런타임 데이터 영역(Runtime Data Area)

JVM이라는 프로그램이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다.

런타임 데이터 영역은 6개의 영역으로 나눌 수 있다.

스레드마다 하나씩 생성되는 영역

  • PC 레지스터(PC Register)
    • PC(Program Counter) 레지스터는 스레드가 시작될 때 생성된다.
    • 현재 수행 중인 JVM 명령의 주소를 갖는다.
  • JVM스택(JVM stack)
    • 스레드가 시작될 때 생성된다.

    • 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택이다.

    • JVM은 오직 JVM 스택 프레임을 추가하고 (push) 제거하는(pop) 동작만 수행한다.

    • 예외 발생 시 printStackTrace() 등의 메소드로 보여주는 StackTrace의 각 라인은 하나의 스택 프레임을 표현한다.

    • ❓ 스택 프레임이란?

      • JVM 내에서 메소드가 수행될 때마다 하나의 스택 프레임이 생성되어
        해당 스레드의 JVM 스택에 추가되고 메소드가 종료되면 스택 프레임이 제거된다.
      • 각 스택 프레임은 지역 변수 배열(Local Variable Array), 피연산자 스택(Operand Stack), 현재 실행중인 메소드가 속한 클래스의 런타임 상수 풀에 대한 레퍼런스를 갖는다.
      • 지역 변수 배열, 피연산자 스택의 크기는 컴파일 시에 결정되기 때문에 스택 프레임의 크기도 메소드에 따라 크기가 고정된다.
        • 지역 변수 배열 : 0부터 시작하는 인덱스를 가진 배열이다. 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장된다.
        • 피연산자 스택 : 메서드의 실제 작업 공간이다. 각 메서드는 피연산자 스택과 지역 변수 배열 사이에서 데이터를 교환하고, 다른 메서드 호출 결과를 추가하거나(push) 꺼낸다(pop). 피연산자 스택 공간이 얼마나 필요한지는 컴파일 할 때 결정할 수 있으므로, 피연산자 스택의 크기도 컴파일 시에 결정된다.
  • 네이티브 메소드 스택(Native Method Stack)
    • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다.
    • JNI(Java Native Interface)를 통해 호출하는 C/C++등의 코드를 수행하기 위한 스택으로,
      언어에 맞게 C 스택이나 C++ 스택이 생성된다.
  • 메소드 영역
    • 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다.
    • JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, Stack 변수, 메소드의 바이트 코드 등을 보관한다.
    • 메소드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다.
    • 메소드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.
    • JVM 벤더란 JVM 제공사 혹은 판매사이다.
      ex) 오라클, Julu 등
  • 런타임 상수 풀
    • 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역
    • 메소드 영역에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다.
    • 각 클래스와 인터페이스의 상수뿐만 아니라, 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다.
    • 즉 어떤 메소드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
    • 인스턴스 또는 객체를 저장하는 공간으로 가비지 컬렉션 대상이다.
    • JVM 성능 등의 이슈에 가장 많이 언급되는 공간이다.
    • 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더의 재량이다.

스레드가 공유해서 사용하는 영역

  • 힙(Heap)
  • 메서드 영역(Method Area)
  • 런타임 상수 풀(Runtime Constant Pool)

❓ JVM에서 Java Stack 메모리를 Stack 자료구조로 구현한 이유는?

  • 스택 프레임(Stack Frame)
    - 메모리의 스택(Stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역이다. 스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸합니다.
    - 함수가 호출되면 스택에는 함수의 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소 값, 함수에서 선언된 지역 변수 등이 저장됩니다.
    - 이렇게 스택 영역에 차례대로 저장되는 함수의 호출 정보를 스택 프레임이라고 합니다.
    이러한 스택 프레임 덕분에 함수의 호출이 모두 끝난 뒤에, 해당 함수가 호출되기 이전 상태로 되돌아갈 수 있습니다.
    - 스택 프레임에 대한 이해를 바탕으로 위 사진을 참고하면 왜 Stack 자료구조를 사용하는지 알 수 있습니다.
    함수 안에서 다른 함수를 호출하면 위의 사진과 같이 쌓이게 되는 것입니다.
    그리고 마지막으로 호출한 함수가 리턴 값이 있다면, 값을 리턴하고 먼저 소멸되게 됩니다.
    호출한 순서의 역순으로 함수를 Stack 메모리에서 소멸되게 됩니다.

3. 실행 엔진(Execution Engine)

  • 클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행된다.
  • 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.
    CPU가 기계 명령어를 하나씩 실행하는 것과 비슷하다.
  • 바이트코드의 각 명령어는 1바이트짜리 OpCode와 추가 피연산자로 이루어져 있으며,
    실행 엔진은 하나의 OpCode를 가져와서 피연산자와 함께 작업을 수행한 다음, 다음 OpCode를 수행하는 식으로 동작한다.
  • OpCode란 명령코드로 기계어의 일부이며 수행할 명령어를 나타내는 부호를 말한다.
  • 그런데 자바 바이트 코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며, 그 방식은 다음 두 가지가 있다.

  • 인터프리터

    • 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다.
    • 하나씩 해석하고 실행하기 때문에 바이트 코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다.
      흔히 얘기하는 인터프리터 언어의 단점을 그대로 가지는 것이다.
    • 바이트 코드라는 ‘언어’는 기본적으로 인터프리팅 방식으로 동작한다.
  • JIT(Just-In-Time) 컴파일러

    • 인터프리터의 단점을 보완하기 위해 도입되었다.
    • 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메소드를 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식이다.
    • 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.

JDK와 JRE의 차이


JDK와 JRE는 자바 프로그래밍의 핵심 개념들이라 자바 개발자라면 꼭 알아야 한다.

❓JDK(Java Development Kit)란?

  • JDK는 개발자가 자바 기반 애플리케이션 개발을 위해 다운로드하는 소프트웨어 패키지다.
  • JRE, 인터프리터/loader(Java), 컴파일러(javac), 아카이버(jar), Documentation 생성기(Javadoc), 기타 자바 개발에 필요한 도구가 포함되어 있다.
  • JDK는 개발자들이 JVM과 JRE에 의해 실행되고 구동 될 수 있는 자바 프로그램을 생성할 수 있게 해준다.
  • 자바 기반 소프트웨어를 개발하기 위한 도구들로 이뤄진 패키지

❓JRE(Java Runtime Environment)란?

  • JRE는 JVM을 생성하는 디스크 상의 부분이다.
  • 자바 코드를 실행 하기 위한 도구들로 구성된 패키지
  • 단지 자바 프로그램을 구동하기 위한 독립형 구성 요소이며, 동시에 JDK의 일부이기도 하다.

🔗 참고자료

  • 위키백과(자바 가상 머신) => 링크
  • Naver D2-JVM Internal ⇒ 링크
  • Oracle - VM(가상 머신)이란 무엇입니까? ⇒ 링크
  • The Java Virtual Machine ⇒ 링크
  • Dzone - The JVM Architecture Explained ⇒ 링크
  • Difference between Byte Code and Machine Code ⇒ 링크
  • IF(kakao) - JVM warm up ⇒ 링크
  • RealJavaOnline - Java Virtual Machine(JVM) ⇒ 링크
  • 자바 JIT 컴파일러 ⇒ 링크
  • Java(Compiler, Interpreter, JIT) 유튜브 영상 ⇒ 링크
  • JVM의 클래스 로더란? ⇒ 링크
  • JIT Compiler ⇒ 링크
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글