[java] JVM 이란

jomminii·2022년 9월 7일
2

whiteship-java-study

목록 보기
1/2

백기준님 라이브 스터디 따라하기
JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가
자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.

  • JVM 이란 무엇인가

JVM(Java Virtual Machine)

JVM은 자바로 작성된 프로그램을 실행시켜주기 위한 가상 컴퓨터라고 보면 됩니다.

일반적으로 프로그램은 OS에 종속적으로 개발이 되었지만 JVM 은 OS 와 프로그램 사이에 위치해 OS 와 상관 없이 프로그램을 실행할 수 있게 해줍니다. 이게 가능한 이유는 각 OS에 맞게 제공된 JVM이 자바를 실행할 수 있게 도와주기 때문입니다.

참고로 JVM 은 자바 프로그래밍 언어에 대한건 아무것도 모르고 특정 바이너리 형식인 클래스 파일 형식만 알고 있습니다.

이로 인해 자바의 주요한 장점 중 하나인 "Write once, run anywhere.(한 번 작성하면 어디서든 실행된다.)" 가 가능해집니다.

(이미지 출처 : platform-engineer)


세 부분으로 이루어진 JVM

JVM specification

JVM 은 소프트웨어 사양 명세라고 볼 수 있는데 구현할 때 최대한의 창의성을 허용하기 위해 명세에서는 세부사항까지는 정의하고 있지 않음을 강조하고 있습니다.

JVM 을 올바르게 구현하려면, 그저 class file 을 읽고 지정된 작업을 올바르게 수행할 수 있게 하기만 하면 됩니다.

따라서 JVM 을 구현할 때는 그저 Java 를 올바르게 실행하는 것 뿐이라고 말하고 있습니다. 이 외에는 각자의 구현에 맡기는 거죠.

JVM implementation

JVM 사양을 구현해서 만든 실제 소프트웨어 프로그램으로 OpenJDK의 HotSpot JVM이 가장 일반적으로 사용되는 JVM 이라고 합니다.

JVM instance

구현된 JVM이 소프트웨어 제품으로써 배포가 되어 그걸 다운 받으면 이걸 JVM 인스턴스라고 합니다.


JVM 의 주요 기능

자바를 어느 장치나 어느 OS 에서도 실행 가능하게 해주고, 프로그램 메모리를 최적화하고 관리해줍니다. 자바가 나온 1995년에는 모든 컴퓨터 프로그램은 특정 os에 맞게 개발되었고, 메모리 또한 개발자들에 의해 관리되었는데 JVM이 이걸 해결해줬습니다.

(이미지출처 : 오라클)


클래스 로더(Class Loader)

자바 프로그램을 실행하기 위해서 JVM은 바이트 코드로 컴파일 된 .class 파일을 읽어내야합니다.

클래스 로더는 컴파일된 코드를 읽어 클래스를 운영체제로 부터 할당 받은 메모리 영역인 Runtime Data Area에 적재해 실행할 수 있도록 하는데, lazy-loading 과 캐싱 같은 기술을 이용해 클래스 로딩을 효율적으로 만들어줍니다.

JVM은 RAM에 상주하며 실행하는 동안 클래스 파일을 RAM으로 가져옵니다. 이걸 동적 클래스 로딩이라고 부르며, 컴파일 때가 아닌 런타임에 처음 클래스를 참조할 때 .class 파일을 load, link, initialize 합니다.

Loading

클래스 로딩 단계에서 클래스 로더는 주어진 바이트 코드를 JVM의 메소드 영역에 로드 및 저장합니다. 로더는 3가지 종류가 있습니다.

  • BootstrapClassLoader : 다른 모든 클래스 로더의 부모가 되는 로더로 BootStrapClassPath(jdk/jre/lib/ 의 rt.jar) 에서 자바 라이브러리 클래스를 로드하는 역할을 합니다. 다른 로더와 다르게 탑재되는 운영체제에 맞게 네이티브 코드로 쓰여졌습니다.
  • ExtensionClassLoader : 부트스트랩 클래스 로더에서 실패하면 ExtensionClassPath(jdk/jre/lib/ext) 에서 사용자 정의 클래스 또는 자바 라이브러리 클래스를 로드하는 역할을 합니다.
  • ApplicationClassLoader/SystemClassLoader: ApplicationClassPath 에서 사용자 정의 클래스를 로드하는 역할을 합니다. 개발자들이 짠 클래스 파일들을 JVM 에 탑재하는 역할을 합니다.

위의 클래스 로더를 모두 거쳤는데도 클래스 파일을 찾지 못하면 ClassNotFoundException 예외를 던집니다.

Linking

로드된 클래스 파일을 검증하고 사용할 수 있게 준비하는 과정 입니다.

  • verification : 클래스 파일이 유효한지 확인합니다. 클래스 로더가 바이트 코드를 자바 언어 명세에 따라 잘 작성했는지, JVM 규격에 따라 검증된 컴파일러에서 생성됐는지 등을 확인하여 정확성을 보장합니다. 클래스 로드 중 가장 오래걸리는 프로세스이지만, 바이트 코드를 실행할 때 여러번 검사를 수행할 필요가 없으므로 효율적 입니다.

    공격자가 클래스 파일을 수정해 공격을 시도해도 바이트코드 검증기를 통해 정상적인 파일인지 확인하고, 검증에 실패하면 java.lang.VerifyError를 발생시킴

  • preparation : 클래스 및 인터페이스에 필요한 static field 메모리를 할당하고, 기본 값으로 초기화합니다. 코드에서 작성한 초기 값은 초기화 단계에서 할당되므로 아직 초기화 블록이나 초기화 코드는 실행되지 않습니다.(ex) int 는 0으로, boolean 은 false로)
  • resolution : Symbolic Reference(심볼릭 참조) 값을 JVM의 메모리 구성 요소인 Method Area 의 런타임 상수 풀을 통해 Direct Reference(직접 참조) 라는 메모리 주소 값으로 바꿔줍니다. 추상적인 기호를 구체적인 값으로 동적으로 결정하는 과정입니다. 이 단계에 영향을 받는 요소는 new, instanceof 가 있습니다. 이 요소들은 런타임 상수 풀에 있는 심볼릭 참조를 사용하고, 이를 실행하려면 먼저 심볼릭 참조를 해석해야합니다.

Initialization

초기화 단계에서 static 블록이 실행되고, 정적 변수가 초기값으로 초기화됩니다.


Runtime Data Area

런타임 데이터 영역은 JVM 프로그램이 OS 에서 실행될 때 할당되는 메모리 영역 입니다.

클래스 로더는 바이너리 데이터를 생성하고 각 클래스에 대해 다음 정보를 메서드 영역에 저장합니다.

  • 로드된 클래스 및 해당 직계 상위 클래스의 정규화된 이름
  • .class 파일이 Class/Interface/Enum과 관련이 있는지 여부
  • 수정자, 정적 변수 및 메서드 정보 등

그 다음 로드된 모든 .class에 대해 java.lang 패키지에 정의된 대로 힙 메모리에 파일을 나타내기 위해 정확히 하나의 Class 객체를 생성합니다.


Method Area(쓰레드 간 공유)

메서드 영역은 JVM 당 하나만 존재하는 공유 리소스 입니다. 모든 JVM 쓰레드는 같은 메서드 영역을 공유하기 때문에 모든 메서드 데이터에 대한 접근과 dynamic linking 프로세스는 thread safe 해야합니다.

메서드 영역은 정적 변수와 같은 클래스 레벨의 데이터를 저장합니다.

  • 클래스 로더 참조(ClassLoader reference)
  • 런타임 상수풀(Run time constant pool) - 숫자 상수, 필드 참조, 메서드 참조, 속성 값. 각 클래스와 인터페이스의 상수 뿐만 아니라 메서드와 필드의 모든 참조값을 포함하고 있습니다. 메서드와 필드가 참조되면 JVM은 런타임 상수풀에서 메서드와 필드의 실제 주소를 검색합니다.
  • 필드 데이터(Field data) - 각 필드의 이름, 타입, 수정자, 속성
  • 메서드 데이터(Method data) - 각 메서드의 이름, 반환타입, 파라미터 타입, 수정자, 속성
  • 메서드 코드(Method code) - 각 메서드의 바이트코드, 피연산자 스택 크기, 로컬 변수 크기, 로컬 변수 테이블, 예외 테이블, 예외 테이블 등

Heap Area(쓰레드 간 공유)

힙 영역도 JVM 당 하나만 존재하는 공유 리소스 입니다. 모든 객체의 정보와 해당 인스턴스 변수 및 배열은 힙 영역에 저장됩니다. 메서드와 힙 영역은 멀티 쓰레드에서 메모리를 공유하기 때문에 이 영역들에 저장된 데이터는 thread safe 하지 않습니다. 힙 영역은 GC의 좋은 타겟 입니다.


Stack Area(쓰레드 당 생성)

스택 영역은 공유 리소스가 아닙니다. 쓰레드가 시작되면 쓰레드마다 메서드 호출을 저장하기 위해 각자의 런타임 스택이 생성됩니다. 모든 메서드 호출에 대해 하나의 항목이 생성이 되어 런타임 스택의 맨 위에 추가(push)되며 이러한 항목을 스택 프레임이라고 합니다.

각각의 스택 프레임은 실행된 메서드가 속한 클래스의 로컬 변수 배열, 피연산자 스택, 런타임 상수 풀에 대한 참조를 가지고 있습니다. 로컬 변수 배열, 피연산자 스택의 사이즈는 컴파일 될 때 결정되며, 이로 인해 메서드에 따라 스택 프레임의 사이즈가 결정됩니다.

스택 프레임은 정상적으로 메서드가 return 을 하거나 메서드 호출 중 예기지 못한 예외가 발생하게 되면 제거(popped) 됩니다. 또한 예외가 발생하면 stack trace의 각 라인은 하나의 스택 프레임을 나타냅니다.(printStackTrace() 와 같은 메서드로 표시됨) 그리고 스택 영역은 공유 리소스가 아니기 때문에 thread safe 합니다.

(이미지 출처 : platform-engineer)

스택 프레임은 세개의 하위 항목으로 나뉩니다.

JVM stack과 frame - 기계인간 John Grib 에서 예시 참조

  • Local Variable Array : 지역 변수 배열은 0부터 시작하는 인덱스를 가지고 있고, 특정 메서드에 대한 관련된 지역 변수와 해당 값들이 이곳에 저장됩니다. 0은 메서드가 속한 클래스의 인스턴스 참조이며, 1부터 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 다음에 지역 변수들이 저장됩니다.
  • Operand Stack : 메서드 내 계산을 위한 작업 공간 입니다.
  • Constant Pool Reference : 런타임 상수풀에 대한 참조를 갖고 있습니다.

메모리 관리 기능

JVM 실행 엔진

클래스 로딩이 끝나면 JVM의 실행엔진(execution engine)은 각각의 클래스를 실행하기 시작합니다. 코드를 실행한다는 것에는 시스템 리소스 접근을 관리하는 것과도 관련이 있습니다. 실행 엔진은 파일, 네트워크, 메모리 리소스 등을 요구하는 프로그램과 이 자원들을 제공하는 os 시스템 사이에 위치하고 있습니다.

실행 엔진은 어떻게 시스템 리소스를 관리하는가

시스템 리스소는 넓게 나눠보면 메모리와 그 밖의 것으로 들 수 있습니다. JVM 이 사용되지 않는 메모리를 처리한다고 했는데 가비지 컬렉션이 이 처리를 담당합니다. JVM 실행 중에 자바 프로그램에서 사용되지 않는 메모리를 식별하고 제거합니다.

그리고 JVM은 참조구조를 할당하고 관리하는 일도 하는데, 예를 들면 자바의 new 키워드 같은 걸 메모리 할당을 위한 OS별 요청에 맞게 처리하는 역할을 합니다.

참고

자바의정석 chapter 1.4 JVM
What is the JVM? Introducing the Java Virtual Machine - InfoWorld
[Java] 자바 JVM 내부 구조와 메모리 구조에 대하여 - 코딩팩토리
How the new JVM works and its Architecture - knoldus
JVM에 관하여 - Tecoble
JVM. 클래스 로더 서브시스템 - hexabrain
Understanding JVM Architecture - Thilina Ashen Gamage
JVM stack과 frame - 기계인간 John Grib

profile
고민은 격렬하게, 행동은 단순하게

2개의 댓글

comment-user-thumbnail
2023년 5월 29일

안녕하세요 :) 글 잘 읽었습니다.
실례가 되지 않는다면 출처를 밝히고 따로 정리해도 괜찮을까요?

1개의 답글