Java Architecture
자바는 "Write Once Run Everywhere"라는 철학으로 시작되어 발전된 프로그래밍 언어이다. 그리고 이러한 철학을 실현하기 위해 네 가지 상호 연관된 기술을 엮어놓았다.
- The Java Programming Language
- The Java Class File Format
- The Java Application Programming Interface (Java API): Runtime 라이브러리 집합
- The Java Virtual Machine
기술명 | 주요 설명 | 비고 |
The Java Programming Language | 객체지향 언어. 생산성 좋음. |
그 외에도 멀티쓰레딩, GC, Dynamic Linking, Dynamic Extension 등 많은 기술이 접목되어 있음 |
The Java Class File Format | Java로 작성한 코드를 컴파일러를 통해 Class 파일로 재생성되는 과정을 거친다. | 주요 특징 4가지 - Compact한 형태, Bytecode로의 변경, 플랫폼 독립적, 네트워크 Byte Order 사용. |
The Java Application Programming Interface (Java API) | Runtime 라이브러리 집합 | OS 시스템과 Java 프로그램 사이를 잇는 가교(Interface) 역할. |
The Java Virtual Machine | 4가지 기술 중 가장 핵심. 자바 프로그램, WAS 등 자바 언어로 작성된 모든 것들을 실행. | Class 파일을 로드하여 ByteCode 해석 및 메모리, 리소스 할당하고 관리한다. JVM은 Thread 관리 및 GC와 같은 메모리 재생 작업도 수행한다. |
Dynamic Linking 이란?
Dynamic Linking이라는 기술 덕분에 Class파일의 크기를 작게 유지할 수 있다. Java의 Class파일은 엄밀히 말하면 실행 가능한 형태로 변경된 것이 아닌 JVM이 읽을 수 있는 형태로 번역된 것으로 이해할 수 있다. 이것은 JVM위에서 다시 실행 가능한 형태로 변형된다. 실행을 위한 Linking작업은 그때 일어나게 된다. Class파일은 실행시 Link를 할 수 있도록 Symbolic Reference만을 가지고 있다. 이 Symbolic Reference는 Runtime시점에서 메모리상에서 실제로 존재하는 물리적인 주소로 대체되는 작업인 Linking이 일어나게 되는 것이다. 이러한 Link 작업은 필요할 때 마라 동적으로 이루어지기 때문에 이를 가리켜 Dynamic Linking이라고 한다.
Java 프로그램 동작 과정
1. Compile Time
개발자들이 자바 소스 코드를 작성하면 .java 파일이 만들어진다. 그리고 실행을 하면 컴파일을 통해 class 파일의 형태로 변환된다. 보통 JDK에 내장되어 있는 'javac'라는 complier를 이용하여 수행한다. 이 작업을 거치면 소스 파일 이름은 같지만 .class라는 확장자를 가진 바이트 파일 생성된다.
2. Run Time
프로그램을 실행하기 위해서 'java'라는 명령어로 JVM을 하나의 프로세스로 올리는 작업과 함께 .class 파일의 로딩도 수행한다. 그 이후 Java API와 더불어 프로그램을 수행한다.
The Java Virtual Machine(JVM)
JRE는 자바 API와 JVM으로 구성되어 있다. JRE에서 가장 중요한 요소는 자바 바이트코드를 해석하고 실행하는 JVM(Java Virtual Machine)이다.
Virtual Machin(가상 머신)은 프로그램을 실행하기 위해 물리적 머신(즉, 컴퓨터)와 유사한 머신을 소프트웨어로 구현한 것을 말한다고 할 수 있다. 위에서 말했다시피 자바는 'Write Once Run Everywhere'라는 철학으로 시작된 프로그래밍 언어이다. 그래서 물리적인 머신과 별개의 JVM이라는 가상 머신을 기반으로 동작하도록 설계되었다. 그래서 자바 바이트코드를 실행하고자 하는 모든 하드웨어에 JVM을 동작시킴으로써 자바 실행 코드를 변경하지 않고도 모든 종류의 하드웨어에서 동작되게 한 것이다.
JVM 동작 과정
JVM은 Class Loader System을 통해 Class 파일들을 JVM으로 로딩한다. 로딩된 Class 파일들을 Execution Engine을 통해 해석된다. 이렇게 해석된 프로그램은 Runtime Date Areas에 배치되어 실질적인 수행이 이루어지게 된다. 이러한 실행 과정 속에서 JVM은 필요에 따라 Thread 동기화와 Garbage Collection 같은 관리 작업을 수행한다.
1. Class Loader
JVM에서 Class 파일의 실행은 Class Loader를 통해 로딩되면서 시작된다.
2. Execution Engine
Class Loader가 JVM내의 Runtime Data Area의 Method Area에 바이트코드를 배치하면 Execution Engine이 .class 파일을 실행한다.
실행 방식은 두 가지를 혼합하여 사용한다.
2-1. Interpreter 방식
자바가 플랫폼에 독립적이고, 이식성이 높은 언어인 이유는 인터프리터 덕분이다. 이는 바이트코드를 한 줄씩 해석하여 실행하는 방식이다. 각 플랫폼에 맞는 인터프리터가 바이트 코드를 실행하기 때문에 Windows, Linux, Mac 어디에서든 실행될 수 있다. 인터프리터는 바이트 코드를 읽고(read), 운영체제가 실행할 수 있도록 기계어로 변경하는 역할을 수행한다.
- 그러나 속도가 느리다는 단점이 있다.
2-2. JIT 컴파일러 방식
인터프리터의 속도 개선을 위해 JIT 컴파일러 방식을 도입했다. JIT 컴파일러는 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법으로, 자주 실행되는 바이트 코드를 런타임 중 기계어로 컴파일하여 사용하는 것이다. 같은 코드를 매번 해석하는 대신 처음 실행될 때 인터프리트를 하면서 자주 쓰이는 코드를 캐싱한 뒤, 이후에는 캐싱된 코드를 가져다 쓰기 때문에 인터프리터의 느린 실행 속도를 개선할 수 있다.
JVM이 처음 시작되면 수천 가지 메서드가 호출된다. 이렇게 모든 메서드를 컴파일하면 결국 프로그램이 매우 우수한 최대 성능을 달성하더라도 시작 시간에 상당한 영향을 줄 수 있다. 각 메서드에 대해 JVM은 사전 정의 된 컴파일 임계 값에서 시작하여 메서드가 호출 될 때마다 감소되는 호출 계수를 정의한다. 호출 계수가 0에 도달하면 메서드에 대한 just-in-time 컴파일이 시작된다.
따라서 자주 사용되는 메서드는 JVM이 시작된 직후에 컴파일되며 사용되지 않는 메서드는 훨씬 나중에 컴파일되거나 전혀 컴파일되지 않는다. JIT 컴파일 임계 값은 JVM이 빨리 시작하고 성능이 향상되도록 도와준다. 임계 값은 시작 시간과 장기간 성능 사이의 최적의 균형을 얻기 위해 선택되었다.
3. Runtime Data Areas (가장 핫한 관심)
Process로서 JVM이 프로그램을 수행하기 위해 OS로부터 할당받는 메모리 영역이다. 이 영역은 Java App, 특히 WAS를 사용할 때 가장 빈번하게 성능문제가 발생하는 영역이기도 하다. Memory Leak이나 Garbage Collection에서 발생하는 문제가 이에 해당한다.
Runtime Data Areas는 각각의 목적에 따라 5개의 영역으로 나뉜다. 그것은 각각 PC Registers, Java Virtual Machine Stacks, Native Method Stacks, Method Area, Heap이라는 명칭으로 되어있다.
- PC Register와 두 개의 Stack 영역은 각 Thread 별로 생성이 된다. (Thread-Safe)
- Method Area와 Heap은 모든 Thread에게 공유된다. (Thread-Unsafe)
4. Garbage Collection
Java는 C++같은 언어와 달리 메모리 할당은 개발자가 원하는 만큼 할 수 있지만 이미 할당한 메모리를 해제하는 것은 개발자가 할 수 없다. system.gc()나 close()와 같은 함수는 단순 해당 객체 사용을 중지하겠다는 의사표현이지 메모리를 직접적으로 삭제하는 것은 아니다.
메모리 해제란 Runtime Data Areas에 있는 Heap이나 Method Area에 있는 특정한 Object를 Memory에서 삭제한다는 의미로 생각하여야 하고 전체 메모리가 아닌 특정한 객체 단위를 대상으로 한다. 그리고 이를 바로 Garbage Collector가 Garbage Collection을 통해 메모리를 해제함으로써 수행한다.
JVM 문서를 보면 메모리 해제에 대한 내용은 다음이 전부이다. JVM 벤더들은 이 두 줄의 글귀를 보고 그에 맞게 Object와 Heap의 Layout을 구성하고 Garbage Collection에 대한 알고리즘을 적용하여 현재 우리가 사용하고 있는 Garbage Collection을 만들었다.
---
Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated.
힙 스토리지는 object들을 위한 공간이고 자동 스토리지 관리 시스템(GC라고 불림)에 의해 회수된다. object들은 명시적으로 메모리 해제가 되지 않는다.
- The Java® Virtual Machine Specification Java SE 18 Edition Java Virtual Machine (2.5.3 Heap)
---
method area 항목에는 다음의 설명이 추가되어 있다.
The "method area "is created on virtual machine start-up.
Although the "method area" is logically part of the heap, simple implementations may choose not to either
garbage collect or compact it.
method area는 논리적으로 힙의 일부이지만, 선택에 따라 GC나 이를 compact하는 것을 해제할 수 있다.
- The Java® Virtual Machine Specification Java SE 18 Edition Java Virtual Machine (2.5.4 Method Area)
JVM 종류
JVM은 하나의 개념, 스펙(Specification)이다. JVM은 그 누구도 자세한 설계도를 만들어 제공하지 않는다. 단지 JVM은 저렇게 해야 한다는 식의 정의만 필요할 뿐이다. 이러한 표준화된 정의로 여러 JVM 벤더사들은 자신들의 철학을 덧붙여 자신만의 JVM을 만든다. 그렇기 때문에 정통 JVM이라는 같은 것은 없다. 이것 또한 JVM의 주요 특징이다.
현재 가장 널리 상용되는 JVM으로는 오라클이 소유한 두 종류의 JVM으로 썬 마이크로시스템즈에서 개발된 HotSpot과 BEA 시스템에서 개발된 JRockit이 있고, 클린 룸 구현으로는 IBM사의 IBM J9이 있다. 윈도우, 리눅스 등의 환경에서는 대부분 HotSpot이 사용되지만, IBM AIX를 운영체제로 사용하는 경우 IBM J9가 널리 사용된다.
- HotSpot: 윈도우, 리눅스 등의 환경에서는 대부분 HotSpot이 사용. 클라이언트 용 앱에 특화됨.
JRockit: 서버용 프로세스에 특화됨. 높은 성능, 느린 부팅.JRockit은 Oracle에 의해 Hotspot과 통합되었다.- IBM : IBM AIX를 운영체제로 사용하는 경우 IBM J9가 널리 사용된다.
내가 사용하고 있는 JVM은?
public class Main {
public static void main(String[] args) {
System.out.println(System.getProperty("java.vm.name"));
// openjdk 11.0.8 2020-07-14
// Java HotSpot(TM) 64-Bit Server VM
}
}
참고
The Java® Virtual Machine Specification Java SE 18 Edition
'Dot Programming > Java' 카테고리의 다른 글
JVM 3 - Runtime Data Area에서의 객체 생성, 소멸 및 참조 과정 알아보기 (0) | 2022.06.01 |
---|---|
JVM 2 - Runtime Data Area 구조 (0) | 2022.05.31 |
[Play with Java] Java 8로 꼬리재귀 함수 만들기 (Tail Recursion) (3) | 2022.04.10 |
[Play with Java] 자바에서 오버플로우 없이 2개의 INT 평균 구하기 (0) | 2022.02.13 |
[Java] 자바 서블릿과 서블릿 컨테이너 2 - MVC 패턴 도입 (0) | 2022.02.10 |