layout: post
title: "자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기"
date: 2022-04-26T00:00:00-00:00
author: sangyeop
categories: Sproutt-2nd
JVM이란 Java Virtual Machine의 약자로써, 말 그대로 컴퓨터를 추상화 시킨 가상 머신이라고 할 수 있다. 실제 컴퓨터처럼 명령어 집합들이 존재하고, 런타임에 다양한 메모리 영역을 조작할 수 있다.
JVM은 특정 구현 기술이나 호스트의 하드웨어 장치, 운영체제 등을 가리지 않고 사양할 수 있다.
사용자가 작성한 프로그래밍 언어 코드는 .java
로 저장이 되고, 컴파일되어 JVM으로부터 실행될 코드는 하드웨어나 운영체제에 의존하지 않는 바이너리 형식으로 표시되고, class
파일 형식으로 저장이 된다.
JVM은 JDK와 JRE내부에 구성되어 있는데 하단의 그림을 참고하면 도움이 될 것이다.
위 그림을 기반으로 차이를 서술해볼 수 있다.
JRE
컴파일 된 자바 프로그램, JVM, 자바 클래스 라이브러리, 자바 명령어 등을 실행하기 위해 필요한 모든 패키지를 의미한다
JDK
모든 기능을 갖춘 자바용 SDK이다. JRE의 기능을 모두 포함함과 더불어, JIT의 javac
와 javadoc
, jdb
와 같은 도구들도 가지고 있다. 프로그램을 생성하고 컴파일하는 역할을 담당한다.
컴파일 과정을 이해하기 위해서는 JDK에 대해서 이해할 필요가 있다. 그렇다면 JDK 란 무엇일까? JDK는 Java Development Kit의 약자로써, 개발과 자바 코드 실행을 위한 도구들로 구성되어 있다. JDK는 JRE, JVM, JDT(개발/실행 도구)로 이루어져 있다. JRE는 Java Runtime Environment의 약자로 자바 프로그램 실행 환경을 제공하는 패키지이다 (개발과 관련 X). JDT는 컴파일러, 디버거와 같은 다른 개발 도구들로 구성되어 있다.
그렇다면 JDK는 어떻게 컴파일을 할까?
.java
클래스를 바이트코드로 변환하여 .class
형식으로 저장한다.바이트코드란 ?: 바이트코드는 소스 코드에서 소프트웨어 해석용으로 설계되어진 저수준으로 컴파일된 코드이다. 이는 JIT(Just In Time) 컴파일러에 의해 런타임 중에 기계 코드로 번역되어 사용될 수 있다.
.class
파일을 JVM에게 넘긴다3번의 과정에서 바이트 코드에 문제나 위험이 발견되면( e.g. 선언 되지 않은 변수 사용 등 ). 컴파일러는 예외를 던지고 실행을 시키지 않는다.
만약 문제가 발견되지 않으면 JIT(Just In Time) 컴파일러는 코드를 기계어로 변경하고 컴파일을 완료한다.
JIT 란? :
프로그래밍 언어로 작성된 코드를 런타임에 기계어로 변환하는 컴파일러이다. JIT는 런타임에 바이트 코드를 기계어로 변환하는 성능을 개선하는데 도움을 준다.
JIT는 기본적으로 사용이 가능하며, 자바 메서드가 호출되었을때 자동으로 활성화가 된다. JIT는 메서드의 바이트 코드를 기계어로 변환 시켜주는데 "Just In Time" 말 그대로 실행되는 즉시 활성화된다. 이후 JVM은 바이트 코드를 해석하는 대신 바로 호출하여 코드를 사용한다.
JVM이 처음 시작될 때 수 많은 메서드들이 호출되는데, 이를 모두 컴파일 하려면 프로그램을 시작하는데 상당한 시간이 들 수 있지만, JIT가 컴파일한 내용은 프로세서의 시간이나 메모리 사용을 요구하지 않는다.
각 메서드에 대해서 JVM은 메서드가 호출될 때 마다 증가하는 call count(호출 횟수)를 저장한다. JVM은 호출 수가 JIT 컴파일 임계값을 초과할 때까지 메서드를 해석한다. 이 call count를 기반으로 자주 호출되는 메서드들은 JVM이 시작되자마자 컴파일 되며, call count가 적은 메서드들은 더 나중에 컴파일 된다. 메서드가 컴파일 된 후에는 call count 들은 초기화되는 과정을 반복한다.
.class
파일 형식으로 저장되어 있는 바이트 코드를 런타임 데이터 영역(힙)에 로딩시키는 역할을 한다.
클래스로더는 3가지 과정으로 작동한다
Loading
바이트코드는 메서드 영역에 저장되고 클래스들을 힙 메모리영역에 로드한다.
Linking
클래스들과 인터페이스들이 JVM의 런타임 상태로 결합되어 실행이 가능해진다.
Initialization
Linking 섹션에서 연결된 클래스들이 실행된다.
런타임 데이터 영역은 JVM에서 프로그램 실행중 사용되는 데이터 영역을 의미한다. 이 중 일부는 JVM 시작 시 생성이 되고, JVM이 종료 될 때 함께 소멸된다. 그렇다면 나머지 부분은 무엇일까? 스레드 데이터 영역이다. 스레드 데이터 영역은 스레드가 생성될 때 생성되고, 스레드가 소멸될 때 함께 소멸된다.
운영체제를 공부하다보면 컴퓨터 내부의 레지스터 종류 중 pc(Prgoram Counter) 레지스터가 있다는 것을 배웠을 것이다. 현재 실행한 명령어의 주소를 저장한다. pc레지스터는 여러개의 쓰레드를 동시에 실행하기 위해서, 어디까지 실행했는지를 저장해둔 레지스터라고 이해하면 편하다. 마찬가지로 JVM도 한 번에 여러개의 스레드를 실행할 수 있도록 pc 레지스터가 존재한다.
pc 레지스터는 스레드마다 각각 하나씩 생성이 된다.
JVM 스레드에는 각 스레드와 스레드가 가진 스택이 존재한다. 이 스택은 다른 언어들의 스택과 유사하며, 프레임단위로 저장하게 된다.
프레임 이란? : 프레임은 데이터 저장이나, 부분 결과값들, 동적 연결, 메서드 결과값 반환, 예외 발생 등에 사용된다. 새로운 프레임은 메서드가 호출 될 때마다 생성 되고, 메서드의 요청이 끝나면 프레임은 소멸된다.
스레드에서 생성된 프레임은 해당 스레드의 로컬에서만 사용되고, 다른 스레드에서는 이를 참조할 수 없다.
JVM 스택은 고정 크기 또는 동적 확장, 축소가 가능하다.
StackOverflowError
를 발생시킨다OutOfMemoryError
를 발생시킨다.JVM은 모든 스레드들 사이에서 공유되는 힙을 가지고 있다. 런타임 시점에 정의되는 모든 클래스 인스턴스와 배열들의 메모리가 할당되는 데이터 영역이다.
힙 공간은 가상머신이 시작될때 생성된다. 객체에 대한 힙 공간은 흔히 가비지 컬렉터(garbage collector, gc)라고 불리는 관리 시스템에 의해 자동으로 관리된다. 힙의 크기는 정적이거나 동적일 수 있다.
OutOfMemoryError
가 발생한다.JVM은 모든 스레드들 사이에서 공유되는 메서드 영역을 가지고 있다. 메서드 영역은 운영 체제 프로세스에서의 코드 자체가 컴파일 되는 공간과 유사하다고 볼 수 있다. 런타임 상수 풀, 필드 및 메서드 데이터, 클래스 및 인터페이스 초기화 및 인스턴스 초기화 (static 변수 등이 여기에 저장 됨)
OutOfMemoryError
가 발생한다.JVM은 "C 스택"이라고 불리는 기존 스택을 사용해서 native메서드(Java 언어 이외의 언어로 작성된 메서드)를 지원한다. 이는 Java 바이트 코드가 아닌 C/C++ 언어가 실행되기 위해 필요한 공간을 의미한다.
StackOverflowError
OutOfMemoryError