안녕하세요! 조엘입니다!
"처음이라니까요" 시리즈 여섯 번째 주자 바로 JVM입니다. ✨✨
IDE에서 열심히 작성한 코드를 기도 메타와 함께 play 버튼만 누를 줄 알던 과거를 이제 뒤로하고!
이번 기회를 통해 작성한 자바 코드가 실제 하드웨어까지 어떻게 전달되는지 알아보도록 해요. 😁
*** Java 프로그램 실행 flow ***
IDE의 플레이 버튼을 누르면 사실 다음과 같은 과정이 일어나게 되어요.
1. 개발자들은 IDE를 키고 열심히 자바 문법에 맞춰 소스코드(.java) 를 작성합니다.
2. javac 컴파일러를 통해 바이트코드(.class) 를 생성합니다.
3. 해당 바이트코드를 실행할 수 있는 런처를 실행합니다.
4. 런처는 내부적으로 JVM에서 바이트코드를 동작시킵니다.
5. JVM에서는 바이트코드를 읽어 OS가 직접 실행할 수 있는 네이티브 머신 코드로 변환합니다.
이번 포스팅에서는 4, 5단계가 어떻게 내부적으로 진행되는지 알아볼게요!! 💪💪
*** JVM이란? ***
JVM은 자바 바이트코드를 OS와 하드웨어에 특화된 코드로 변환하여 실행하는 프로그램이에요.
Java를 배우다 보면 "Write Once Run Anywhere" 라는 말을 마주할 때가 있어요.
그 어떤 개발 환경에서 자바 코드를 작성하더라도, 다른 임의의 OS와 하드웨어에서 실행될 수 있다는 뜻인데요.
이는 바이트코드를 실행 환경의 OS와 하드웨어가 알 수 있도록 JVM이 변환해주기 때문에 가능했던 일이에요.
자바 바이트코드를 어떻게 이해하고 실행할지, JVM을 구현하기 위한 표준 스펙은 정해져있어요.
(표준 스펙: https://docs.oracle.com/javase/specs/jvms/se11/html/)
이를 구현한 여러 JVM 구현체들이 존재해요. 이를 JVM 벤더라 부르고 오라클, 아마존, Azul 등이 대표적이죠.
위에서 언급했듯 JVM은 자바 바이트코드를 실행시키는 프로그램이기 때문에,
자바 바이트코드를 생성하는 언어라면 JVM을 통해 프로그램을 실행시킬 수 있어요.
코틀린, 그루비 등의 언어들이 JVM에서 돌아갈 수 있는 이유이죠.
아래 링크에서 JVM 위에서 실행될 수 있는 언어들을 확인해볼 수 있어요.
*** JVM 구조 ***
이제 JVM이 자바 바이트코드를 실행 환경의 OS/HW가 실행할 수 있도록 변환해준다는 것은 알았어요.
조금 더 상세하게 이를 어찌 JVM이 해내는지 알아보아요! 🎯🎯
JVM은 크게 3가지 영역으로 구성되어요.
1. 클래스 로더
- 클래스 파일(바이트코드)을 읽고, 클래스 정보를 메모리의 힙/메서드 영역에 저장
2. 메모리
- 실행 프로그램의 정보가 올라가 있는 메모리
3. 실행 엔진
- 바이트 코드를 네이티브 코드로 변환시켜주는 역할 및 동적 메모리 관리
하나씩 상세하게 알아보아요! 🎺
*** 클래스 로더 ***
클래스 로더는 프로그램의 클래스 파일(바이트코드)을 읽고, 정보를 적절한 메모리 위치에 저장해요.
총 3가지 단계로 해당 역할을 수행하는데요.
아래 그림과 같이 Loading >> Linking >> Initialization 순서대로 수행되어요.
Loading
Loading에서는 클래스 로더가 .class 파일을 읽고 적절한 바이너리 데이터 만든 후,
메모리의 메서드 영역에 "클래스의 정보" 를 저장하는 역할을 수행하게 되어요.
여기서 저장하는 "클래스의 정보" 란 다음을 지칭해요.
- Fully Qualified Class Name (패키지 경로 / 패키지 이름 / 클래스 이름)
- 클래스, 인터페이스, 이넘 정보
- 메서드, 변수
이렇게 로딩이 완료된 클래스에 대해 Class라는 객체를 생성하여, 메모리의 힙 영역에 저장해 두어요.
여기서 한 가지 더 공부하고 가야 하는게, 위에 "클래스 로더가 .class 파일을 읽고" 라는 구절인데요.
프로그램을 온전히 실행하기 위해선 직접 작성한 클래스들과 추가해준 라이브러리들 뿐만 아니라,
시스템 프로퍼티, 코어 자바 API (java.lang.Object, java.lang.Class, java.lang.ClassLoader...) 등을 필요로 해요.
JVM에서는 기본적으로 3가지 종류의 클래스 로더를 제공하고, 이를 통해 필요한 클래스들을 가져오게 되어요.
- [Bootstrap ClassLoader - Extension ClassLoader - Application ClassLoader]
Bootstrap ClassLoader에서는, JAVA_HOME\lib의 코어 자바 API를 읽어와요.
Extension ClassLoader에서는, JAVA_HOME\lib\ext의 시스템 프로퍼티를 읽어와요.
Application ClassLoader에서는, 애플리케이션 클래스 패스에 저장된 클래스들을 읽어와요.
(여기에서 개발자의 코드와 프레임워크/라이브러리 코드들이 읽혀요)
Linking & Initialization
Linking에서는 클래스 파일의 검증과 static 변수들에 해당하는 메모리를 준비하도록 해요.
Verify >> Preparation >> Resolve 단계를 거치면서 진행되어요.
Verify 단계에서는 ".class" 파일의 형식이 유효한지를 검증해요.
Preparation 단계에서는 클래스의 static 변수와 기본값에 필요한 메모리를 준비해두어요.
Resolve 단계에서는 심볼릭 메모리 레퍼런스를 실제 메서드 영역에 있는 실제 레퍼런스로 변경해요.
Initialization에서 Linking 과정을 통해 준비해둔 메모리 영역에 static 변수의 값을 할당하게 되어요.
또한 이 단계에서 클래스에서 정의해 둔 static 블럭이 수행되어요.
*** JVM 메모리 영역 ***
이제 JVM은 OS로부터 메모리를 할당받은 후, 해당 메모리를 용도에 따라 여러 영역으로 나누어 관리해요.
JVM 메모리 영역은 다음과 같이 5가지 영역으로 나뉘어요. 역할은 다음과 같아요!
Method(Class) Area
메서드 영역은 공유 자원으로써, 자바 프로그램의 모든 쓰레드에서 공유하게 되어요.
메서드 영역은 클래스 로더를 설명하면서 몇 번 언급이 되었어요.
메서드 영역에서는 클래스 구조를 메타데이터처럼 가지게 되며, 메서드들의 코드를 저장해요.
Heap
힙 영역은 공유 자원으로써, 자바 프로그램의 모든 쓰레드에서 공유하게 되어요.
힙 영역은 어플리케이션 실행 중에 생성되는 객체(인스턴스) 를 저장하는 영역이에요.
힙 영역은 Garbage Collector를 통해 메모리가 관리되어요.
Stack
스택 영역은 각 쓰레드 마다 고유하게 생성되어요.
스택 영역에서는 메서드 호출을 스택 프레임이라 부르는 블럭으로 쌓아요.
스택 영역에서는 로컬 변수, 중간 연산 결과들이 저장이 되어요.
스택 영역은 쓰레드가 종료되면 소멸되어요.
PC Register
PC Register는 각 쓰레드 마다 고유하게 생성되어요.
PC Register는 쓰레드의 현재 실행할 스택 프레임의 주소를 저장하고 있어요.
PC Register는 쓰레드가 종료되면 소멸되어요.
Native Method Stack
Native 메서드 스택 영역은 각 쓰레드 마다 고유하게 생성되어요.
Native 메서드 스택 영역은 C/C++ 등의 Low Level 코드를 실행하는 스택이에요.
Native 메서드 스택 영역은 쓰레드가 종료되면 소멸되어요.
*** JVM 실행 엔진 ***
이제 마지막으로 클래스를 실제로 실행시키는 JVM 실행 엔진입니다. 💪💪
JVM 실행 엔진에서는 자바 바이트코드를 명령어 단위로 읽으며 실행해요.
JVM 실행 엔진은 바이트 코드를 실제 기계가 실행할 수 있는 형태로 변환해 주는데요!
Interpreter와 JIT Compiler, 이 두 가지 방법을 혼용하며 수행해요.
Interpreter
인터프리터는 바이트 코드를 한줄씩 읽고 해석하여 실행하는 방식이에요.
바이트 코드 하나하나의 해석은 빠르지만, 인터프리팅의 결과의 실행이 느려요.
JIT Compiler
이러한 인터프리터의 단점을 보완하고자 JIT 컴파일러가 도입되었어요.
인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일해 네이티브 코드로 변경해요.
이후에는 해당 메서드를 더 이상 인터프리팅 하지 않고 네이티브로 바로 실행해버려요.
또한 실행 엔진에서는 Garbage Collector(GC)가 돌아가고 있어요.
힙 영역이 한정되어 있는데 무한정 새로운 인스턴스를 생성해 올릴 수는 없을 것이에요.
명시적으로 메모리를 비워주는 C, C++ 과 달리, 자바에서는 GC를 통해 메모리를 관리해요.
GC는 메모리에서 더 이상 참조되지 않는 객체를 모아서 정리해주는 역할을 해줘요.
GC는 정말 다룰 내용이 많아서, 개괄적인 부분만 소개하고, 다른 포스팅에서 설명하도록 하겠습니다~ 🤞🤞
GC 포스팅은 여기서 찾아보실 수 있습니다 :)
https://papimon.tistory.com/93
참고
- https://www.inflearn.com/course/the-java-code-manipulation
- https://opentutorials.org/module/2495/13968
- https://loustler.io/languages/how-to-execution-java-in-jvm/
- https://javapapers.com/core-java/java-jvm-run-time-data-areas/#Program_Counter_PC_Register
- https://www.geeksforgeeks.org/jvm-works-jvm-architecture/
- https://blog.jamesdbloom.com/JVMInternals.html
- https://d2.naver.com/helloworld/1230
'프로그래밍 언어 > Java' 카테고리의 다른 글
저는 GC가 처음이라니까요? (0) | 2021.10.02 |
---|---|
저는 어노테이션이 처음이라니까요? (2) | 2021.06.11 |
Reflection API (4) | 2021.06.09 |
HashMap/HashSet의 원리 (4) | 2021.03.13 |
Java Code Conventions (0) | 2020.11.24 |
댓글