[Java] JVM Architecture

JD_S·2022년 11월 13일
0

Java

목록 보기
3/21

JVM이란?

JVM(Java Virtual Machine)은 Java응용 프로그램을 실행하는 런타임 엔진 역할을 한다. JVM은 실제로 자바 코드에 존재 하는 메인 메소드를 호출한다. JRE(Java Runtime Environment)의 일부이다. 자바의 "Write Once Run Anywhere"라는 철학도 JVM이 있어 가능한 것이다.

.java파일을 컴파일 할때 .java파일에 있는 동일한 클래스 이름을 가진 .class파일(바이트 코드 포함)이 Java컴파일러에 의해 생성된다. 이 .class파일은 실행할 때 다양한 단계를 거친다.

클래스 로더 하위 시스템

주로 세 가지 활동을 한다.

  • 로딩 중
  • 연결
  • 초기화

로딩 : 클래스 로더는 .class파일에서 해당 바이너리 데이터를 생성하여 메소드 영역에 저장한다. 각 .class파일에 대해 JVM은 메소드 영역에 다음 정보를 저장한다.

  • 로드된 클래스와 해당 직계 상위 클래스의 정규화된 이름
  • class파일이 Class, Interface, Enum과 관련이 있는지에 대한 여부
  • 제어자, 변수 그리고 메서드 정보 등

.class 파일을 로딩한 후 JVM은 힙 메모리에 이 파일을 나타내는 Class 타입의 객체를 만든다. 이 객체는 java.lang패키지에 미리 정의된 클래스 유형이다. 이러한 Class 객체는 프로그래머가 클래스 이름, 부모 이름, 메서드 및 변수 정보 등과 같은 클래스 수준 정보를 가져오는데 사용할 수 있다. 이 객체 참조를 가져오기 위해 Object 클래스의 getClass()메서드를 사용할 수 있다.

연결 : 확인, 준비 및 (선택 사항) 해결을 수행한다.

  • 확인 : .class파일의 정확성을 보장한다. 즉, 이 파일이 올바른 형식으로 지정되고 유효한 컴파일러에서 생성되었는지 여부를 확인한다. 확인이 실패하면 런타임 예외 java.lang.VerifyError가 발생한다. 이 활동은 ByteCodeVerifier 구성 요소에 의해 실행된다. 이 활동이 완료되면 클래스 파일을 컴파일할 준비가 된 것이다.

  • 준비 : JVM은 클래스 정적 변수에 대한 메모리를 할당하고 메모리를 기본값으로 초기화한다.

  • 해결 : 타입의 심볼릭 레퍼런스를 직접 참조로 바꾸는 과정이다. 참조된 엔티티를 찾기 위해 메서드 영역을 검색하여 수행된다.

심볼릭 레퍼런스 : 참고하는 클래스의 특정 메모리 주소를 참조 관계로 구성한 것이 아니라 참조하는 대상의 이름을 지칭하는 것. JVM에 Class파일이 올라가면 심볼릭 레퍼런스는 그 이름에 맞는 개체의 주소를 찾아서 연결하는 작업을 수행한다. 실제 메모리 주소가 아니라 이름만 가진다.

초기화 : 이 단계에서 모든 정적 변수는 코드 및 정적 블록(있는 경우)에 정의된 값으로 할당된다. 이것은 클래스에서 위에서 아래로, 클래스 계층에서 부모에서 자식으로 실행된다. 일반적으로 세가지 클래스 로더가 있다.

  • 부트스트랩 클래스 로더 : 모든 JVM구현에는 신뢰할 수 있는 클래스를 로드할 수 있는 부트스트랩 클래스 로더가 있어야 한다. "JAVA_HOME/jre/lib"디렉토리에 있는 핵심 자바API클래스를 로드한다. 이 경로는 일반적으로 부트스트랩 경로로 알려져 있다. C/C++과 같은 네이티브 언어로 구현된다.

  • 확장 클래스 로더 : 부트스트랩 클래스 로더의 하위 항목이다. 확장 디렉토리 "JAVA_HOME/jre/lib/ext"(확장 경로) 또는 java.ext.dirs시스템 속성으로 지정된 다른 디렉토리에 있는 클래스를 로드한다. 이것은 sun.misc.Launcher$ExtClassLoader클래스에 의해 자바에서 구현된다.

  • 시스템/응용 프로그램 클래스 로더 : 확장 클래스 로더의 자식이다. 애플리케이션 클래스 경로에서 클래스를 로드하는 역할을 한다. 내부적으로 java.class.path에 매핑된 환경 변수를 사용한다. 또한 sun.misc.Launcher$AppClassLoader클래스에 의해 Java로 구현된다.

참고

JVM은 Delegation-Hierarchy원칙에 따라 클래스를 로드한다.(1. Delegation-Hierarchy원칙이 뭘까?) 시스템 클래스 로더는 확장 클래스 로더에게 로드 요청을 위임하고, 확장 클래스 로더는 부트스트랩 클래스 로더에게 요청을 위임한다. 부트스트랩 경로에서 클래스가 발견되면 클래스가 로드되고 그렇지 않으면 요청이 확장 클래스 로더로 다시 전송된 다음 시스템 클래스 로더로 전달된다. 마지막으로 시스템 클래스 로더가 클래스를 로드하는 데 실패하면 런타임 예외 java.lang.ClassNotFoundException이 발생한다.

JVM 메모리

1.메서드 영역 : 메서드 영역에는 정적 변수를 포함하여 클래스 이름, 직계 부모 클래스 이름, 메서드 및 변수 정보 등과 같은 모든 클래스 수준 정보가 저장된다. JVM당 하나의 메소드 영역만 있으며 공유 자원이다. Java 8부터 정적 변수는 이제 힙 영역에 저장된다.

2.힙 영역 : 모든 객체의 정보가 힙 영역에 저장된다.(인스턴스가 생성되는 공간) JVM당 하나의 힙 영역이 있고 또한 공유 리소스이다.

3.스택 영역 : 모든 쓰레드에 대해 JVM은 여기에 저장되는 하나의 런타임 스택을 생성한다. 이 스택의 모든 블록은 메서드 호출을 저장하는 활성화 레코드/스택 프레임이라고 한다. 해당 메서드의 모든 지역 변수는 해당 프레임에 저장된다. 쓰레드가 종료된 후 런타임 스택은 JVM에 의해 파괴된다. 공유 리소스가 아니다.

4.PC 레지스터 : 쓰레드의 현재 실행 명령의 주소를 저장한다. 분명히 각 쓰레드에는 별도의 PC레지스터가 있다.

5.네이티브 메서드 스택 : 모든 쓰레드에 대해 별도의 네이티브 스택이 생성된다. 기본 메서드 정보를 저장한다.

실행 엔진

실행 엔진은 .class(바이트 코드)를 실행한다. 바이트 코드를 한 줄씩 읽고, 다양한 메모리 영역에 있는 데이터와 정보를 사용하고 명령을 실행한다. 다음 세 가지로 분류할 수 있다.

  • Interpreter : 바이트 코드를 한 줄씩 해석하여 실행한다. 여기서 단점은 하나의 메서드를 여러 번 호출할 때 매번 해석이 필요하다는 것이다.

  • Just-In-Time Compiler(JIT) : 인터프리터의 효율성을 높이기 위해 사용된다. 전체 바이트 코드를 컴파일 하여 네이티브 코드로 변경하여 인터프리터가 반복되는 메서드 호출을 볼 때마다 JIT에서 해당 부분에 대한 직접 네이티브 코드를 제공하므로 재해석이 필요가 없어 효율성이 향상된다.

  • Garbage Collector : 참조되지 않은 객체를 파괴한다. 가비지 컬렉터에 대한 건 추후에 다루겠다.

자바 네이티브 인터페이스(JNI)

Native Method Libraries와 연동하여 실행에 필요한 Native Library(C/C++)를 제공하는 인터페이스이다. JVM이 C/C++라이브러리를 호출하고 하드웨어에 특정한 C/C++라이브러리에 의해 호출될 수 있도록 한다.

Reference

profile
Whatever does not destroy me makes me stronger.

0개의 댓글