[Java] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가

JVM 개념과 구성요소 및 실행과정 / JDK와 JRE 차이

Posted by Wonyong Jang on November 08, 2020 · 11 mins read

목표

자바 소스파일(.java)을 JVM으로 실행하는 과정 이해하기

학습할 것

  • JVM이란 무엇인가
  • 컴파일 하는 방법
  • 실행하는 방법
  • 바이트코드란 무엇인가
  • JIT 컴파일러란 무엇이며 어떻게 동작하는지
  • JVM 구성요소
  • JDK와 JRE의 차이

JVM 이란

1)Java Virtual Machine 은 자바를 실행하기 위한 자바 가상머신이다. 자바와 운영체제 사이에서 중계자 역할을 하며, 자바가 운영체제 종류에 영향을 받지 않고 실행 가능하도록 한다.

2) 운영체제 위에서 동작하는 프로세스로 자바 코드를 컴파일 해서 얻은 바이트 코드를 해당 운영체제가 이해할 수 있는 기계어로 바꿔실행 시켜주는 역할을 한다.

3) GC(Garbage Collection)을 이용하여 자동으로 메모리 관리를 해준다.

자바 소스파일(.java)을 JVM으로 실행 과정

1) 프로그램이 실행되면 JVM은 운영체제로 부터 이 프로그램이 필요로 하는 메모리를 할당 받음

JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리

2) 자바 컴파일러(javac)가 자바 소스코드(.java)를 읽어들여 자바 바이트코드(.class)로 변환
.java -> .class

3) Class Loader를 통해 class파일을 JVM 메모리에 적재

4) JVM 메모리 영역에 적재된 class 파일을 Execution engine을 통해 해석


자바 컴파일 및 실행 방법

자바 소스를 컴파일 및 실행하기 위해서는 기본적으로 아래의 두 프로그램이 필요하다.

  • javac.exe
  • java.exe

javac.exe는 자바 소스코드를 컴파일 할때 사용하는 프로그램이며 컴파일된 바이트코드를 실행할 때 java.exe. 사용한다.

javac.exe는 JDK, java.exe 는 JRE에 포함되어 있기에 JDK과 JRE를 설치해야 하지만 과거와 다르게 요즘은 JDK에 JRE가 포함된 형태로 배포되고 있기에 JDK만 설치해도 무관하다.

1) 컴파일 하는 방법

  • 자바 소스 파일 작성
  • javac.exe 사용하여 .java 파일 컴파일
$ javac 소스파일명.java   

컴파일이 정상적으로 완료되면 해당 경로에 소스파일명 .class 생성

1-1) 컴파일 옵션

JDK 상위 버전으로 컴파일 하고 하위버전 JDK(또는 JRE)로 실행 했을 경우 아래 에러가 발생한다.

Exception in thread "main" java.lang.UnsupportedClassVersionError   

이를 해결 하기 위해서는 상위버전 JDK로 실행을 하던지 javac 옵션을 추가하여 하위버전의 JDK도 실행 가능하도록 한다.

  • Compiling old Java with a newer compiler

버전이 오래된 자바 코드를 버전이 높은 플랫폼에서 재 컴파일 하려는 경우, 보통의 경우는 특정 컴파일 옵션을 줄 필요는 없다.

하지만, 몇가지 경우에는 옵션이 필요한데 아래를 예로 들 수 있다.

enum이 자바 5부터 추가 되면서 아래와 같이 변수명으로 enum은 불가능해 졌다. 하지만, 오래된 코드에서 아래처럼 작성되었다면 새로운 컴파일러로 재컴파일 했을 경우는 에러가 발생한다.

public class OldSyntax {
    private static int enum;  // invalid in Java 5 or later
}

따라서 아래와 같이 컴파일러 옵션을 이용하여 소스코드가 특정 버전으로 컴파일 될 수 있도록 지정한다.

$ javac -source 1.4 OldSyntax.java

Source parameter tells the compiler that the source code will be compiled with the specific version. If you are compiling the source code with Java 1.8, but if the code is having some specific features in the Java 14, then compilation fails. In that case you have to set the source parameter as the java 14

  • Compiling for an older execution platform

오래된 자바 플랫폼에서 높은 버전의 자바 소스를 실행 하려 할 때 에러가 발생한다.
해결책으로는 JDK를 자바 소스코드에 맞춰 업그레이드를 하는게 가장 쉬운 방법이다.

그렇지 않다면 컴파일 옵션을 주어 해결 가능하다.

하지만 여기서도 버전을 낮추는 경우 버전 보다 높은 문법을 사용 했을 경우는 에러가 발생한다.

$ javac -target 1.4 SomeClass.java

Target parameter tells the compiler that what is the minimum targeted JVM that can run the class files. If you pass the target parameter as 1.8, then the class files compatible to run 1.8 and above version of JVM. But, if can not run below 1.8 JVM.

2) 실행하는 방법

java.exe 파일을 사용하여 바이트코드로 컴파일된 .class 실행

$ java 소스파일명

단, 실행시에는 소스 파일명의 확장자는 붙이지 않음


바이너리코드와 바이트코드란 무엇인가

프로그램을 실행하는 것은 결국 컴퓨터이다. 다시 말해 프로그램은 컴퓨터가 이해할 수 있는 형태로 작성되어 있어야 한다.

1) 바이너리코드란?

C언어는 컴파일러에 의해 소스파일(.c)이 목적파일(.obj)로 변환될 때 0과 1로 이루어진 바이너리코드로 변환된다.
즉, 컴파일 후에 이미 컴퓨터가 이해할 수 있는 이진코드로 변환되는 것이다.

목적파일은 기본적으로 컴퓨터가 이해할 수 있는 바이너리코드의 형태이지만 실행될 수는 없다. 그 이유는 완전한 기계어가 아니기 때문 (변화된 목적 파일은 링커에 의해 실행 가능한 실행파일(.ex)로 변환 될 때 100% 기계어가 될 수 있다.

2) 기계어란 ?

기계어는 컴퓨터가 이해할수 있는 0과 1로 이루어진 바이너리코드이다.

기계어가 바이너리코드로 이루어졌을 뿐이지 모든 이진코드가 기계어인 것은 아니다( 바이너리코드 != 기계어)

3) 바이트코드란 ?

c언어와 다르게 Java에서는 컴파일러(javac)에 의해 소스파일(.java)이 컴퓨터가 바로 인식할 수 없는 바이트코드(.class)로 변환된다.

컴퓨터가 이해할 수 있는 언어가 바이너리코드라면 바이트코드는 가상 머신이 이해할 수 있는 언어이다.

고급언어로 작성된 소스코드를 가상 머신이 이해할 수 있는 중간 코드로 컴파일한 것을 말한다.

이러한 과정을 거치는 이유는 어떠한 플랫폼에도 종속되지 않고 JVM에 의해 실행 될수 있도록 하기 위함이다.

여기서 플랫폼이란 개발환경 실행환경 등 어떠한 목적을 수행할 수 있는 환경을 말한다.

ex) 프로그램이 실행되는 환경인 운영체제의 종류(window, mac), 개발이 수행되는 환경의 종류(안드로이드, 비주얼 스튜디오)


JDK와 JRE의 차이

두개의 키워드를 아래와 같이 간단하게 요약할 수 있다.

스크린샷 2020-11-09 오후 7 59 10

출처 : https://www.inflearn.com/course/the-java-code-manipulation

  • JDK = JRE + @ (개발에 필요한 도구)

  • JRE는 읽기 전용, JDK 읽기 / 쓰기 전용이라 생각 할 수 있다.

1) JDK

JDK는 자바 개발도구(Java Development Kit)의 약자이다.
JAVA로 된 언어를 컴파일하고 개발할 수 있도록 해주는 개발 환경의 세트를 의미한다.
JDK는 JRE에서개발을 위해 필요한 도구(javac, java, visualVM 등)을 포함한다.

2) JRE

JRE는 자바 실행환경(Java Runtime Environment)의 약자이다.
JAVA를 개발할 필요는 없는데, 실행은 시켜줘야 하는 경우에는 꼭 JRE가 있어야 한다.
JRE는 JVM이 자바 프로그램을 동작시킬 때 필요한 라이브러리 파일들과 기타 파일들을


JVM 구성요소

스크린샷 2020-03-14 오후 6 35 17

1) Java Compiler

java source(.java)파일은 ByteCode(.class)로 변환된다.

2) Class Loader

변환된 ByteCode(.class)파일을 JVM이 운영체제로 부터 할당 받은 메모리 영역인 Runtime Data Area로 적재하는 역할을 한다.

3) Execution Engine

Class Loader 를 통해 JVM 내부로 넘어와 Runtime Data Area(JVM 메모리)에 배치된 ByteCode들을 기계어로 변경하게 되는데 이때 두가지 방식을 사용하게 된다.
(인터프리터, JIT 컴파일러)

실행 엔진 내부적으로는 인터프리터, JIT, GC가 있다.

1. Interpreter(인터프리터)

기존 바이트 코드를 실행하는 방법은 인터프리터 방식이 기본이다.
실행엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.

이 방식은 한줄씩 수행하기 때문에 느리다는 인터프리터 언어의 단점을 그대로 가지고 있다.

2. JIT(Just-In-Time)

JIT 컴파일러는 실행 시점에 인터프리터 방식으로 기계어 코드를 생성 하면서 그 코드를 캐싱한다. 그리고 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다.

전체 컴파일 후 캐싱 -> 이후 변경된 부분만 컴파일 하고 나머지는 캐시에서 가져다가 바로 실행 한다.

변경된 부분만 컴파일 하기 때문에 수행속도가 인터프리터 방식에 비해 빠르다.

3. GC (Garbage Collection)

자바에서 개발자는 힙을 사용할 수 있는 만큼 자유롭게 사용하고, 더이상 사용되지 않는 오브젝트들은 GC에 의해 자동으로 메모리에서 제거된다.

자세한 내용은 아래 링크를 통해 확인 가능하다.

관련 링크

4) Runtime Data Area

프로그램을 수행하기 위해 운영체제로부터 할당받은 메모리 공간

1. PC Register

Thread가 시작될 때 생성되는 공간으로 Thread마다 하나씩 존재한다.
Thread가 어떤 부분을 어떤 명령으로 실행해야 할 지에 대한 기록을 하는 부분

2. Stack Area

프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역

Stack 영역은 Thread별로 각각 독립적으로 생성된다.

각종 형태의 변수나 임시데이터, 스레드나 메소드의 정보를 저장한다.
호출된 메서드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다.

3. Heap Area

new 연산자로 생성된 객체와 배열을 저장하는 메모리 공간

Stack과 Heap 메모리관리 관련 링크

4. Native method stack

Java가 아닌 다른 언어로 작성된 코드를 위한 공간
자바 프로그램이 컴파일되어 생성되는 바이트 코드가 아닌 실제 수행할 수 있는 기계어로 작성된 프로그램을 실행 시키는 영역

5. Method Area

클래스와 인터페이스의 정보를 처음 메모리 공간에 올릴 때 초기화 되는 대상을 저장하기 위한 메모리 공간

Method Area는 모든 Thread에 의해 공유되는 영역이며, JVM이 시작될 때 생성된다.

런타임 상수풀(runtime constant pool), Field, Method, constructor 등 클래스와 인터페이스와 관련된 데이터들을 분류하고 저장한다.


Reference

https://goodgid.github.io/Java-JDK-JRE/
https://shrtorznzl.tistory.com/82
https://annajinee.tistory.com/42
https://github.com/whiteship/live-study/issues/1
https://javabeat.net/javac-source-target/