JVM과 자바 코드의 실행 방법

Tina Jeong·2020년 12월 30일
1

Re-자바

목록 보기
1/16

아마 지인들은 나와 JAVA를 함께 떠올릴 것 같다. 그만큼 자바를 좋아하고 주력 언어로 사용해왔지만, 자바 전반에 대해 기록한 적은 없다. 이번 스터디 시리즈를 통해 좀더 깊이 이해하고 좀더 친해질 생각이다.

JVM은 무엇이며 자바 코드는 어떻게 실행하는가?

자바 코드는 텍스트 형태로 작성된다. .java가 확장자이다. 이 .java 파일을 javac 명령어를 통해 자바 컴파일러가 .class 파일로 변환해준다. .class 파일은 특정 CPU에 종속되는 형태가 아니고, Bytecode라는 소스코드와 기계어 중간 쯤의 형태intermediate representation로 기술되어 있다. Bytecode는 JVM 즉, Java Virtual Machine자바가상머신이 입력으로 받아 사용한다. JVM은 Bytecode를 OS 환경에 맞게 인스턴스화 하여 프로그램을 실행하는 역할을 맡는다.


이미지 출처 : https://docs.oracle.com/javase/tutorial/getStarted/intro/definition.html

컴파일 방법(커멘드창에서)

여기 .java가 확장자인 텍스트 파일이 있다.

public class HelloWorld {

        public static void main (String[] args) {
                System.out.println("Hello World!");
        }
}

해당 파일을 javac 명령어를 통해 컴파일 할 수 있다. 컴파일을 하면 HelloWorld.class 파일이 생성된다. 엄밀히 말하면 javac 명령어는 front-end적인 컴파일만 한다. 기계어가 아닌 바이트코드를 생성해내기 때문이다. 그래서 자바의 맥락에서 컴파일을 의미할 때는 단순한 파싱이 아닌 기계어를 생성하는 JIT Compiler를 먼저 떠올려야 한다.

$ javac HelloWorld.java
$ ls
HelloWorld.class  HelloWorld.java

class 파일은 대강 아래와 같은 내용이었다. javap 명령어를 사용하면 class 파일의 내용을 human readable한 형태로 보여준다.

$ javap -c HelloWorld.class
Compiled from "HelloWorld.java"
public class HelloWorld {
  public HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String Hello World!
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

실행 방법

.class파일이 생성됐다면 java 명령어를 통해 JVM 보고 일을 시켜서 프로그램을 실행할 수 있다.

$ java HelloWorld
Hello World!

JVM

Write once, Run anywhere : Portablity

앞서 언급했듯이, 바이트코드와 자바가상머신은 한 프로그램만 짜서 다양한 환경에서 돌아갈 수 있는 이식성Portability 을 실현하는 역할을 한다. 똑같은 상황과 맥락은 아니지만, 프로젝트 하나 짜서 안드로이드와 iOS 환경 모두에서 돌아가는 어플을 만들 수 있도록 서포트해주는 기술Flutter을 연상하면 이해가 쉬울 것 같다.


이미지 출처 : https://docs.oracle.com/javase/tutorial/getStarted/intro/definition.html

Just-in-Time Compiler : Runtime Optimization

프로그램이 돌아가는 동안, 자바 가상 머신에 포함된 JIT Compiler는 바이트코드를 기계어로 번역해 사용할 뿐 아니라, 런타임에 발생하는 것들을 모니터링하고 실행과정을 최적화해서 성능을 향상 시킨다. 즉, 반복적으로 사용되는 기계어가 있다면 JIT Compiler가 캐싱해둔다. 이후 재컴파일하고 해당 기계어가 재호출 되면, 기계어로 다시 interpret하는 과정없이 바로 실행해 사용한다. 결과적으로 JIT Compiler는 캐싱 등의 전략을 통해 runtime optimization을 성취함으로써, 일반적인 interpreter와의 성능적 차별성을 확보할 수 있다. 고로 자바를 처음 배울 때 자주 언급되는 자바 성능 이슈는 Critical하게 고려되어야 할 정도는 아니며, 상용적으로 쓰이기에 부족함이 없다.

JVM의 구성 요소

JVM은 3개의 서브시스템으로 이루어진다.

Class Loader

Class Loader는 자바에서의 동적 클래스 로딩을 담당한다. Class Loader는 특정 클래스 A가 처음 참조될 때(compile 타임이 아닌 runtime에) 클래스 A를 load하고, link하고, initialize한다.

이름설명
loadBootStrap,Extension,Application 3개의 로더가 특정 path에 포함된 클래스들을 구분하고 분업해 로딩을 해준다.
linkBytecode verifier가 생성된 Bytecode를 검증한다(Verify).
static 변수 메모리가 할당되고, default value들로 채워진다(Prepare).
logical reference들이 method 영역의 original reference로 대체된다(Resolve).
initializestatic변수들에 사용자가 할당한 original value가 대입되고, static block이 실행된다.

📌 고로 static variable도 runtime 중 class loading time에 메모리 할당 된다. 당연히 C처럼 compile time에 올라갈 거라고 생각했음..

Runtime Data Area

Runtime Data Area는 5개 요소로 구별된다.

영역이름설명
Method AreaJVM에서 method area는 하나만 존재하고, 그렇기 때문에 method area의 자원들은 공유자원이며 thread-safe하지 않다. method area에는 static variable을 비롯한 클래스 레벨의 모든 데이터들이 저장된다.
Heap AreaJVM에서 heap area도 하나만 존재하는 공유자원이고, thread-safe하지 않다. heap area에는 array, object 등의 인스턴스들이 저장된다.
Stack Area스레드 별로 하나의 runtime stack이 생성되므로 thread-safe하다. 메소드 호출이 발생할 때 마다 stack 내부적으로 지역변수, 실행할 연산, exception catch 정보, 메소드 관련 정보들이 저장되는 Stack Frame이 생성된다.
PC Registers스레드 별로 PC Register들이 생성되고, PC Register에서는 현재 작업의 주소가 저장되고, 다음 명령으로 넘어가면 다음 작업의 주소로 업데이트 된다.
Native Method stacks스레드 별로 Native Method stack이 생성된다. Native method는 Java에서 다른 언어의 라이브러리를 호출해서 사용하거나, 이미 존재하는 C/C++ 프로젝트를 Java로 통합할 때 사용한다.

Execution Engine

Execution Engine은 bytecode의 내용이 올라간 Runtime Data Area의 데이터를 가지고 프로그램을 실행한다. Execution Engine에는 bytecode를 one by one 기계어로 번역하는 Intepreter와, Interpreter의 단점을 극복한 JIT Compiler, 사용자의 메모리 관리 없이도 사용되지 않은 자원을 자동 할당 해제하는 Garbage Collector로 이루어져있다.

JDK vs JRE

JRE는 Java Runtime Environment의 약자로, 자바 프로그램을 실행시키는 데 필요한 라이브러리, java 명령어, JVM 등을 의미한다. 환경적인 요소이기 때문에 JRE만 가지곤 새로운 프로그램을 개발할 순 없다.

JDK는 Java Development Kit의 약자로, JRE 뿐 아니라 새로운 프로그램을 개발하기 위한 라이브러리, javac 명렁어 등을 포함한다. 자바 프로그래머는 JDK를 받아 사용한다. Java 9버전부터는 JRE가 따로 만들어지지 않고, JDK에 패키징되어 출시되는 중이다.

실험

윈도우에서 javac한 파일을 우분투 터미널에서 실행할 때 발생하는 오류

tina@~~:/mnt/c/Users/Tina/git$ java HelloWorld
Error: LinkageError occurred while loading main class HelloWorld
java.lang.UnsupportedClassVersionError: HelloWorld has been compiled by a more recent version of the Java Runtime (class file version 59.0), this version of the Java Runtime only recognizes class file versions up to 58.0

윈도우에는 java se 15.0.1 버전이 깔려있고, 리눅스는 openjdk 14.0.2 버전이 깔려있었기 때문이다. 즉, OS 문제가 아니라 JDK 버전 문제였다. 하위 버전의 바이트코드는 상위 버전의 바이트코드를 실행할 수 없다. 반대의 경우는 가능하다. 상위버전에서는 하위 버전의 바이트코드를 실행할 수 있다. 아니면 상위 버전에서 -source-target옵션을 주어서 버전을 조정할 수도 있다.

  • -source 옵션: 특정 자바 버전과의 소스 호환성을 제공하는 옵션 (즉, 특정 자바 버전으로 컴파일 하도록). 자바 15에서는 자바 7버전 부터 소스 호환성을 지원한다. ex) -source 14
  • -target 옵션 : 특정 자바 버전에 맞는 class 파일을 생성하도록 지정하는 옵션. ex) -target 14

참고
https://docs.oracle.com/javase/tutorial/getStarted/intro/definition.html
https://www.oracle.com/java/technologies/architecture-neutral-portable-robust.html#319
https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
https://dzone.com/articles/jvm-architecture-explained
https://en.wikipedia.org/wiki/Just-in-time_compilation
https://www.baeldung.com/java-native
Java in a Nutshell, 7th Edition

계속해서 문서를 업데이트하고 있습니다. 언제든지 댓글피드백 남겨주세요. 😉

profile
Keep exploring, 계속 탐색하세요.

0개의 댓글