프로그래밍 일기 — JVM과 컴파일러
Java의 마법 주문을 구현하는 장치
Java는 아무리 생각해도 정말 놀라운 마법이다. 그 마법 주문대로 스크린 상에서 많은 것들이 만들어질 수 있기 때문이다. 그렇다면 이 Java의 마법을 현실화 시키는 장치들은 무엇일까?
JVM(Java Virtual Machine)(2)
Java를 활용해 프로그램을 실행할 수 있게 해주는 가상머신으로써, 디바이스에 상관없이 이 것이 설치된 장비에서는 어느 곳이든 Java로 만든 프로그램을 실행가능하다.
JVM은 bytecode형태의 Java코드를 불러오고, 검증하고, 실행한다. 즉, 인간의 언어(영어)로 써진 high-level의 언어를 컴퓨터의 기계어인 어셈블리 언어로 해석해 주는 역할을 하는 것이다. 쉽게 말해 컴퓨터와 인간사이의 통역사 역할을 한다고 볼 수 있다.
JVM의 장점은 특정 플랫폼에 의지하지않고, 메모리 관리 및 보안 설정 등 다양한 기능을 수행할 수 있다는 것이다. 또한, 다른 프로그래밍 언어로 작성되었더라도 Java bytecode로 변환이 된다면 해당 프로그램을 실행할 수 있다.
JVM은 말 그대로 가상머신이다. 가상머신이란 서버로써, 특정 하드웨어에서 여러개의 어플리케이션이 동시에 실행될 수 있게 해준다. 이는 리소스의 더 나은 활용을 가능하게 해주며, 이전에 존재한 다른 형태의 인프라보다 더 쉽고 효율적으로 스케일링이 가능하게해준다. 어플리케이션을 더 이상 사용할 필요가 없다면 가상머신을 종료하면 된다.
JVM은 크게 아래 3가지 컴포넌트로 나뉘어진다.
- Class Loader Subsystem: Java 클래스 파일을 불러오고, 연결하고, 초기화할 수 있게 해주며, Dynamic Class Loading이라고도 불린다.
- Runtime Data Areas: 메서드 영역, PC 레지스터, Stack 영역 및 쓰레드를 포함한다.
- 실행 엔진(Execution Engine): Interpreter, Compiler 및 Garbage Collection 영역을 포함한다.
Java 컴파일 과정
컴파일링(Compilation)은 Java 소스 코드를 컴퓨터가 해석 가능한 byte코드로 바꾸어주는 과정을 말한다. 해석이란, JVM가 Java byte코드를 직접 실행는 과정을 말한다. Java 컴파일러는 소스 코드를 통해 byte코드를 생성하고, JVM이 실행할 수 있게 해준다.
JNI(Java Native Interface)
JVM에서 실행되는 Java코드로하여금 특정 하드웨어나 운영체제플랫폼에 종속된 어플리케이션과 커뮤니케이션 할 수 있게 해주는 프로그래밍 프레임워크다. 이러한 어플리케이션은 특정 환경이나 조건에 종속되어 있기에 Native한 어플리케이션이라 부르며, 다른 언어로도 쓰여질 수 있다. Native 메서드는 다른 언어로 쓰여진 Native 코드를 Java 어플리케이션으로 옮길 수 있게 해준다.
JRE(Java Runtime Environment)
Java 프로그램 혹은 어플리케이션이 현재 시스템에서 실행될 수 있도록 해주는 소프트웨어 도구를 말한다. JRE는 Java객체에 대한 동적메모리할당(Dynamic Memory Allocation)을 위해 Heap 공간을 사용하며, JDB(Java Debugging)에도 활용된다.
JRE는 Java 명령어를 통해 Java 프로그램을 실행하려면 필요한 도구이다. 만약 실재로 어떠한 프로그램을 개발하거나 컴파일 하지 않는다면 JRE만 있으면 된다.
JRE는 보통 아래 컴포넌트로 나뉘어 진다.
- Deployment solutions: Java Plugins이나 Java Web Start등 어플리케이션을 활성화하는 것을 도와주고 Java 업데이트를 지원하는 도구들을 포함한다.
- Development toolkits: 개발자가 유저 인터페이스를 개발하는데 있어 도움을 주는 개발 도구로써, Java 2D, Abstract Window Toolkit(AWT), Swing등을 포함한다.
- Integration libraries: JRE는 여러 통합 라이브러리 및 클래스 라이브러리를 제공하여 개발자가 어플리케이션과 서비스 사이에서 깔끔한 데이터 연결을 할 수 있도록 돕는다. Java IDL, JDBC API, JNDI등을 포함한다.
- Language 및 Utility 라이브러리:
Java.lang.
및Java.util.
패키지등 Java 어플리케이션을 개발하고, 패키지 버전 관리 및 모니터링을 지원한다. Collections framework, 동시성(Concurrency) utility들, Preferences API, 로깅(Logging), Java Archive(JAR)등이 포함된다.
JVM의 구조(3)
JVM은 Java 어플리케이션을 실행하기 위한 런타임 엔진같은 역할을 하는데, 바로 여기서 JVM이 Java코드내 main
메서드를 호출한다. JRE가 JVM을 포함하기에 여기서 JRE가 런타임 환경을 제공하는 것이다.
Java 어플리케이션은 WORA(Write-Once-Run-Anywhere)라 불리며, 한번 특정 시스템에서 작성하면 어느 다른 JVM이 존재하는 환경에서도 특별한 재설정 없이 실행할 수 있다.
컴파일링의 과정은 아래와 같다.
.java
파일을 컴파일하면, 컴파일러는 byte코드를 포함하는.class
파일에.java
파일에 정의된 같은 이름의 클래스를 생성한다.- 이
.class
파일은 Class Loader통해 읽어오며, 해당 클래스 파일에 대한 Binary 데이터를 생성해 메서드 영역에 저장한다. 각.class
파일에 대해 JVM은 아래 정보를 메서드 영역에 저장한다.
- 불러온 클래스와 해당 클래스의 부모 클래스의 이름 전체
.class
파일이Class
,Interface
, 혹은Enum
인지 여부- 수정자(modifier), 변수 및 메서드 정보 등
3. .class
파일을 읽어온 후, JVM은 Heap 메모리 영역에서 이 파일을 표현하기 위한 Class
타입의 객체를 생성한다. 주의할 것은 이 객체는 java.lang
패키지에서 미리 정의한 Class
타입이라는 것이다. 이러한 Class
객체는 개발자로 하여금 클래스의 이름, 부모 이름, 메서드 및 변수 정보 등을 알 수 있게해준다. 이 객체의 참조를 얻기 위해서는 Object
클래스의 getClass()
메서드를 활용할 수 있다.
4. 다음으로 연결(Linking) 즉, 검증(Verification), 준비(Preparation), Resolution를 포함하는 과정이 진행된다.
- 검증(Verification):
.class
파일이 맞는지를 확인하는 과정으로써, 이 파일이 올바르게 포맷되었는지, 올바른 컴파일러에 의해 생성되었는지를 파악한다. 만약 이 과정이 실패하면java.lang.VerifyError
라는 런타임 예외가 발생한다. 이 과정은ByteCodeVerifier
라는 컴포넌트에 의해 수행되며, 이후 클래스 파일은 컴파일링을 할 수 있는 준비 상태가 된다. - 준비(Preparation): JVM은 클래스의
static
변수에 대해 메모리를 할당하고 메모리에 해당 변수에 대한 기본 값을 초기화한다. - Resolution: 간접적인 상징적 참조타입을 직접 참조하도록 바꾸는 과정이다. 참조된 Entity를 메서드 영역에서 찾아 루어진다.
5. 초기화(Initialization)과정이 이루어진다. 이 과정에서 모든 static
변수가 코드내 혹은 static
블록(하나라도 존재할 경우)에서 정의된 값을 지정받는다. 클래스내에서 위에서 아래 방향으로 실행되며, 부모클래스에서 자녀클래스 방향으로 실행된다.
JDK(Java Development Kit)
JRE를 포함하는 개발 도구로써, Java 어플리케이션의 개발을 가능하게하는 키 컴포넌트를 가진다. 특정 운영체제 플랫폼에따라 설치되어야할 파일이 다르다.
JDK는 Java에서 Java 플랫폼을 통해 개발된 프로그램을 컴파일링, 디버깅, 실행할 수 있게 해주는 모든 도구를 포함한다. Java프로그램은 CLI사용하여 실행될 수 있다. JDK는 Java프로그램을 실행하는데 필요한 모든 Java 도구 및 실행파일들과 Binary파일들을 포함한다. 즉, Java개발에 사용되는 JRE, 컴파일러, 디버거 및 Archiver와 다른 도구를 포함한다.
Java SE(Standard Edition) vs Java EE(Enterprise Edition)
일반적으로 Java를 말할 때, Java SE 혹은 Core Java를 말한다. 이 세가지는 모두 타입 및 객체를 포함하는 가장 기본적인 Java 스펙을 말한다. Java EE는 더 큰 규모의 어플리케이션을 실행할 때 필요한 API를 말한다. 따라서 Java SE가 가장 일반적으로 이야기하는 Java의 형태라 할 수 있다.
보너스: Java의 퍼포먼스를 증가시키는 법
컨테이너(Container)
가상머신(VM)에서 발전된 기술로, 더 효율적인 추상화와 OS 커널의 가상화를 지원한다. 컨테이너 자체는 OS가 없기 때문에 가상머신과 비교해 더 가볍고, 빠르고, 유연하다.
JIT(Just-in-time Compiler)
JVM의 일부로써 Byte코드를 기계어로 변환하는데 있어 더 최적화 된 성능을 제공한다. 비슷한 Byte코드를 한꺼번에 컴파일하여 컴파일링이 전체적으로 빨라졌다.
Javac
Java내 정의(definitions)를 읽어와서 JVM에서 바로 실행할 수 있는 byte코드로 번역하는 컴파일러다.
Javadoc
Java 소스 코드의 API Documentation을 HTML로 변환한다. 이는 HTML에서 표준화된 Documentation을 생성하는데 매우 유용하다.
요약
Java를 활용하기 위해서는 위해서는 반드시 JVM, JRE, JDK가 같이 있어야하며, 이 모두가 정상적으로 각자의 역할을 해야한다.
- JDK는 개발 플랫폼으로써 Java프로그램을 실행시킬 수 있는 도구인 JRE를 포함한다.
- JVM은 Java 프로그램의 심장으로써, Java 소스코드가 특정 플랫폼에 국한되지 않고 활용될 수 있도록 해준다.
- Java는 JDK와 JRE를 포함하며, Java프로그램은 JVM없이 실행될 수 없다.
참조:
(1) https://pixabay.com/illustrations/a-book-mysticism-magic-mystical-1769625/
(2) https://www.ibm.com/blog/jvm-vs-jre-vs-jdk/
(3) https://www.geeksforgeeks.org/jvm-works-jvm-architecture/