JVM

Minjoo Kim·2024년 9월 28일

JVM(Java Virtual Machine)은 자바로 작성된 프로그램을 실행하기 위한 환경. 자바 소스코드를 CPU가 인식할 수 있는 기종 고유의 코드로 변환하여 실행한다. 그래서 운영체제에 종속받지 않고 CPU가 자바를 인식, 실행할 수 있다. JVM이 있어서 자바 언어는 운영체제에 독립적이다.

자바 응용프로그램은 운영체제나 하드웨어가 아닌 JVM하고만 통신하고 JVM이 자바 응용프로그램으로부터 전달받은 명령을 운영체제가 이해할 수 있도록 변환하여 전달한다. JVM은 운영체제에 종속적이다.

Write Once, Read Anywhere

자바 애플리케이션은 JVM을 한 번 더 거치고 하드웨어에 맞게 완전히 컴파일된 상태가 아니고 실행 시 해석(interpret)되기 때문에 속도가 느리다.

JVM 구조

1 Classloader

자바 응용프로그램을 실행할 때마다 클래스 파일(.class)들을 로드하는 JVM의 서브시스템. Java에는 세가지 빌트인 클래스로더가 있다.

  1. Bootstrap ClassLoader : Extension ClassLoader의 상위 클래스인 첫 번째 클래스 로더. java.lange, java.net, java.util, java.io, java.sql 패키지 클래스 등과 같은 Java Standard Edition의 모든 클래스 파일이 들어 있는 rt.jar 파일을 로드한다.

  2. Extension ClassLoader : Bootstrap ClassLoader의 자식 클래스로더이고 System ClassLoader의 부모 클래스로더. $JAVA_HOME/jre/lib/ext 디렉토리 안에 위치한 jar 파일들을 로드한다.

  3. System/Application ClassLoader : Extension ClassLoader의 자식 클래스. classpath로부터 classfile들을 로드한다. 기본적으로 classpath는 현재 디렉토리로 세팅된다. -cp-classpath 명령어를 사용해 classpath를 바꿀 수 있다.

클래스로더를 만들고싶다면 ClassLoader 추상 클래스를 상속받아야 한다.

2 Class(Method) Area

로드된 클래스들의 메타데이터와 관련된 정보를 저장하는 영역. 런타임 상수 풀, 필드 및 메서드 데이터, 메서드 코드와 같은 클래스별 구조가 저장된다.

3 Heap

객체가 동적으로 할당되는 런타임 데이터 영역. Heap에 할당된 객체에 대한 참조는 스택 메모리에 저장된다. 전역 접근이 가능하므로 응용프로그램의 어느 곳에서나 접근할 수 있다(어떤 메서드, 클래스, 스레드).

  • generations
  1. Young Generation
    모든 새 객체가 할당되고 노화되는 곳. 가득 차면 가비지 컬렉션이 발생한다.
    Eden Space와 Survivor Spaces로 나뉜다.
  2. Old or Tenured Generation
    오래 살아남은 객체가 저장되는 곳. 객체가 Young Generation에 저장되면 객체 나이에 대한 임계값이 설정되고 이 임계값에 도달하면 해당 객체는 여기로 이동한다.
  3. Permanent Generation
    런타임 클래스와 애플리케이션 메서드에 대한 JVM 메타데이터로 구성된다.
  • 복잡한 메모리 관리 기술을 통해 객체의 수명에 따라 메모리를 효율적으로 관리한다.
  • 힙 영역이 꽉 차면 java.lang.OutOfMemoryError를 던진다.
  • 스택에 비해 접근이 비교적 느리다.
  • 스택과 반대로 자동으로 할당 해제되지 않는다. 메모리를 효율적으로 관리하기위해 사용되지 않는 객체를 가비지 컬렉터가 필요하다.
  • 스택과 달리 힙은 스레드 안전하지 않으므로 코드를 적절히 동기화해 보호해야 한다.

4 Stack

메서드 호출 시마다 프레임을 생성되는데 여기에 로컬 변수와 부분 결과 및 메서드 호출과 실행에 필요한 정보를 저장한다(로컬 변수, 운영 결과, 메서드 호출 정보). 메서드 호출의 순서를 관리해 프로그램의 실행 흐름이 올바르게 이어지도록 돕는다. Java에서는 멀티스레딩을 지원하기 때문에 각 스레드는 자신의 JVM 스택을 소유한다. 메서드가 호출될 때마다 새 프레임이 생성되고 프레임은 메서드 호출이 완료되면 소멸된다. Last-In-First-Out(LIFO).

  • 새로운 메서드가 호출되고 반환됨에 따라 커지고 줄어든다.
  • 스택 안에 있는 변수는 해당 변수를 생성한 메서드가 실행되고 있는 동안만 존재한다.
  • 메서드 실행이 완료되면 자동으로 할당 및 할당 취소된다.
  • 메모리가 꽉 차면 java.lang.StackOverFlowError를 던진다.
  • 힙에 접근할 때보다 스택 접근이 더 빠르다.
  • 각 스레드는 자신의 스택을 가지므로 스레드 안전(threadsafe)하다.

5 Program Counter Register(PC register)

현재 실행 중인 JVM 명령어의 주소를 포함한다. 현재 실행되고 있는 바이트코드 명령어의 주소를 추적해 JVM이 프로그램의 실행 흐름을 관리하고 올바른 명령어를 순차적으로 실행할 수 있도록 돕는다. 각 스레드는 독립적인 PC 레지스터를 갖는다.

6 Native Method Stack

JVM 내에서 네이티브 메서드(Java가 아닌 다른 프로그래밍 언어로 작성된 메서드)를 실행하는 데 사용되는 메모리 영역. 네이티브 메서드는 시스템 수준의 작업을 수행하거나 기존 네이티브 라이브러리를 활용하기 위해 사용된다.

7 Execution Engine

Java 바이트코드를 실행하는 역할. 바이트코드를 기계어로 변환하여 하드웨어가 이해할 수 있도록 한다.

  1. 가상 프로세서(Virtual Processor)
    바이트코드 명령어를 실행하는 가상 프로세서. 물리적인 CPU의 동작을 모방해 Java 응용프로그램을 다양한 운영체제에서 수정없이 실행할 수 있게 한다.
  2. 인터프리터(Interpreter)
    바이트코드 스트림을 한 번에 하나의 명령어씩 읽어 직접 실행한다.
  3. JIT 컴파일러(Just-In-Time Compiler)
    런타임 중 자주 실행되는 바이트코드를 네이티브 코드로 컴파일해 성능을 향상시킨다.

8 Java Native Interface(JNI)

Java 응용프로그램이 다른 프로그래밍 언어로 작성된 코드와 상호작용할 수 있게 해주는 프레임워크. Java의 이식성을 유지하면서도 성능이 중요한 부분이나 기존 네이티브 라이브러리와 통합을 가능하게 한다.


🔗 References

profile
Hello, this is Minjoo Kim.

0개의 댓글