JVM 탄생 이전
C/C++ 는 컴파일 플랫폼과 타겟 플랫폼이 다를 경우 프로그램이 동작하지 않는다.
플랫폼 = 운영체제 + CPU 아키텍처
문제 발생 원인
- 하드웨어 아키텍처 차이
- 서로 다른 CPU 아키텍처는 명령어 집합이 다르기 때문에 x86 아키텍처에서 컴파일된 프로그램은 ARM 기반의 프로세서에서 실행할 수 없음
- CPU는 각 아키텍처에 맞는 어셈블리 명령어를 해석하기에, 서로 다른 아키텍처 간에는 명령어 해석이 달라질 수 밖에 없음
- 운영체제 차이
- 운영체제마다 시스템 호출 방식, 메무리 구조 등이 다를 수 있음
이러한 문제를 해결하기 위한 과정을 cross compile이라 한다.
C/C++은 cross compiler를 통해 타겟 플랫폼을 정의하고 이에 맞춰서 컴파일을 진행하는 방식으로 문제를 해결했음
e.g. Linux에서 windows를 타겟 플랫폼으로 정의하고 컴파일
이렇게 탄생한 target program은 윈도우에서 정상적으로 작동
Java는 JVM을 통해 문제를 해결하였음
- Java source code가 javac를 통해 컴파일을 거쳐 Java Bytecode로 변환
- Java Bytecode는 운영체제나 하드웨어에 종속적이지 않은 중간 코드
- Java Bytecode를 JVM의 클래스로더에게 전달
- class loader는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역, 즉 JVM의 메모리에 올림
- class loader의 세부 동작
- 로드: 클래스 파일을 가져와서 JVM의 메모리에 로드
- 검증: 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사
- 준비: 클래스가 필요로 하는 메모리를 할당. (필드, 메서드, 인터페이스, ...)
- 분석: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
- Symbolic Reference: 클래스 파일이 있는 참조를 텍스트 형태로 나타낸 것 e.g. 클래스 이름, 메서드 이름, 필드 이름, ...
- Direct Reference: 실제 메모리 주소나 오프셋을 가리키는 포인터로 JVM 메모리 내의 실제 위치를 나타냄
- 초기화: 클래스 변수들을 적절한 값으로 초기화 (static 필드)
- class loader의 세부 동작
- JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행, 이때 실행 엔진은 두가지 방식으로 변경
- 인터프리터: 바이트 코드 명령어를 하나씩 읽어서 해석, 하나하나의 실행은 빠르지만, 전체적인 실행 속도가 느림
- JIT 컴파일러: 인터프리터의 단점을 보완하기 위해 도입된 방식, 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경, 이후 메서드를 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식
- 인터프리터 방식에 비해 빠른 실행속도
WORA (Write Once Run Anywhere)
JVM 내부 구조
JVM이 Java Bytecode를 실행하기 위해 사용하는 메모리 공간으로
Method Area와 Heap은 모든 스레드가 공유하며 Stack, Pc register, Native method stack은 각 스레드마다 존재함
Method Area
클래스 로더가 클래스 파일을 읽어오면, 클래스 정보를 파싱해서 Method Area에 저장
Heap
프로그램을 실행하면서 생성한 모든 객체를 Heap에 저장
Program Counter(pc)
각 스레드는 메서드를 실행하고 있고, pc는 그 메서드 안에서 몇 번째 줄을 실행해야 하는지 나타내는 역할
Stack
- 스택 프레임은 메서드가 호출될 때마다 새로 생겨 Stack에 push됨
- 스택 프레임은 Local variables array, Operand stack, Frame Data를 갖고 있음
- Local variables array: 메서드가 사용하는 지역 변수와 매개 변수, 연산 중 발생하는 중간 결과나 값을 일시적으로 저장하는 공간
- Operand stack: 메서드가 연산을 수행할 때 사용하는 피연산자와 그 결과를 저장하는 스택
- Frame Data: Constant Poll, 이전 스택 프레임에 대한 정보, 현재 메서드가 속한 클래스/객체에 대한 참조 등의 정보를 갖고 있음
Navtie Method Stack
자바에서 Native Method(자바 외부 언어로 작성된 메서드 -> C/C++, ...)를 호출할 때 사용되는 스택 공간
Native Method는 JVM 밖에서 플랫폼 의존적으로 실행되기 때문에, 이를 처리하는 별도의 스택이 필요
코드로 보는 연산 과정 예시
public class Main {
public static void main(String[] args) {
double position = 1.0;
double initial = 1.0;
double rate = 1.0;
position = initial + rate * 60;
}
}
// ...
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
DCONST_1 // Operand Stack에 1.0을 push
DSTORE 1 // Operand Stack에서 pop한 값을 Local Variable Array 1번 인덱스에 저장
L1
LINENUMBER 7 L1
DCONST_1 // Operand Stack에 1.0을 push
DSTORE 3 // Operand Stack에서 pop한 값을 Local Variable Array 3번 인덱스에 저장
L2
LINENUMBER 8 L2
DCONST_1 // Operand Stack에 1.0을 push
DSTORE 5 // Operand Stack에서 pop한 값을 Local Variable Array 3번 인덱스에 저장
L3
LINENUMBER 10 L3
DLOAD 3 // Local Variable Array 3번 인덱스에 있는 값을 Operand Stack에 push
DLOAD 5 // Local Variable Array 5번 인덱스에 있는 값을 Operand Stack에 push
LDC 60.0 //Load Constant Pool
// 이러한 값들은 자주 사용될 수 있기 때문에 Constant Poll에 저장되었으며, 가져오는 과정
DMUL // Operand Stack에서 두 값을 pop, 곱셈 연산 이후 다시 push
DADD // Operand Stack에서 두 값을 pop, 덧셈 연산 이후 다시 push
DSTORE 1 // Operand Stack에서 pop한 값을 Local Variable Array 1번 인덱스에 저장
L4
LINENUMBER 11 L4
RETURN
L5
// ...
출처
https://medium.com/@mayurwaghmode/cross-compiling-c-c-code-for-ppc64le-on-x86-64-6226ff3a80b7
https://techvidvan.com/tutorials/java-virtual-machine/
https://www.youtube.com/watch?v=UzaGOXKVhwU&list=WL&index=3
'Java' 카테고리의 다른 글
JDBC란 (0) | 2025.01.01 |
---|---|
record (2) | 2024.12.01 |
직렬화(Serialization) (2) | 2024.12.01 |
고유 락 (Intrinsic Lock) (1) | 2024.11.29 |
Java에서의 Thread (0) | 2024.11.28 |