우선 내가 여태 알고있던 Java의 실행 과정이다.
1. 개발자가 작성한 .java 파일을 컴파일러가 .class(바이트코드 파일)로 변경한다.
2. JVM의 클래스 로더가 .class를 메모리에 적재시킨다.(런타임 시점)
3. JVM의 실행 엔진이 메모리에 적재된 바이트코드를 실행 시킨다.
간단하게 위 단계의 개념으로 알고있었는데, 조금더 세세하게 내부 개념을 정리해보려고 한다.
컴파일 과정
Java를 설치하면 javac.exe의 형태로 컴파일러가 먼저 설치된다.
자바 컴파일러가 개발자가 작성해놓은 .java 파일을 .class(바이트 코드)로 변경시켜주는 역할을 한다.
자바 코드
public class Human{
private final String name;
private final int age;
public Human(String name, int age) {
this.name = name;
this.age = age;
}
}
바이트 코드
// class version 61.0 (61)
// access flags 0x21
public class Human {
// compiled from: Human.java
// access flags 0x12
private final Ljava/lang/String; name
// access flags 0x12
private final I age
// access flags 0x1
public <init>(Ljava/lang/String;I)V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 6 L1
ALOAD 0
ALOAD 1
PUTFIELD Human.name : Ljava/lang/String;
L2
LINENUMBER 7 L2
ALOAD 0
ILOAD 2
PUTFIELD Human.age : I
L3
LINENUMBER 8 L3
RETURN
L4
LOCALVARIABLE this LHuman; L0 L4 0
LOCALVARIABLE name Ljava/lang/String; L0 L4 1
LOCALVARIABLE age I L0 L4 2
MAXSTACK = 2
MAXLOCALS = 3
}
전통적인 컴파일 언어(C, C++ 등)
- 컴파일러가 고수준 언어를 기계어로 컴파일하여 CPU가 실행.
자바
- 컴파일러가 고수준 언어를 바이트 코드로 컴파일하여 JVM이 실행.
- 바이트 코드는 중간 형태의 코드.
- 실행 엔진이 기계어로 변환시켜서 최종적으로 CPU가 실행
- 중간 단계가 추가돼서 느리다는 특징이 있다.
결국 자바는 JVM이라는 추가 단계가 들어가기에 느리다는 단점이 발생한다.
그러나 JVM을 사용하기에 제일 독보적인 특징이 있다. 바로 OS에 종속적이지 않다.
OS 위에 JVM을 설치하고 자바를 실행시킨다. 즉 JVM만 있으면 바이트 코드를 실행시킬 수 있다.
그러나 JVM은 OS마다 종속적이라는 특징이 존재한다.
JVM
JVM의 구성은 다음과 같이 이루어져 있다.
1. 클래스 로더
2. 실행 엔진
3. 런타임 데이터 영역
클래스 로더
클래스 로더는 의미 그대로 클래스를 메모리에 로딩하는 역할을 담당한다.
모든 클래스는 런타임 시점에 필요할 때마다 클래스 로더가 로딩을 진행한다.
클래스 로드 과정은 크게 3단계로 구분된다.
1. 로딩 단계
- 클래스를 파일에서 가져와서 메모리에 로드하는 단계
2. 링킹 단계
- 검증 : 바이트 코드가 자바의 규칙에 맞게 설정됐는지 검증.
- 준비 : 클래스가 필요로하는 메모리를 미리 할당
- 분석 : 클래스가 참조하는 객체의 실제 주소값을 할당
3. 초기화 단계
- static 변수의 값을 할당한 다음에 초기화 진행
클래스 로더는 아래 3단계로 동작을 진행한다.
1. 캐시 확인
- 요청을 받으면 캐시에 있는지 확인
2. 상위 클래스 로더에게 위임
- 캐시에 없으면 상위 클래스 로더에게 위임
3. 자기 자신을 확인
- 상위도 전부 없으면 자기 자신을 확인
그림을 보면 이해하기가 더 쉬울 것이다.
1. 애플리케이션 클래스 로더 호출
2. 애플리케이션 클래스 로더는 본인의 캐시 확인, 없으면 확장 클래스 로더에게 위임
3. 확장 클래스 로더는 본인의 캐시 확인, 없으면 부트스트랩 클래스 로더에게 위임
4. 부트스트랩 클래스 로더는 본인의 캐시 확인, 없으면 클래스 패스에 찾음.
5. 찾지 못하면 하위 클래스 로더에게 다시 위임 -> 반복
이 과정을 진행하게 되고, 만약에 하위 클래스 로더에서도 찾지 못하면 ClassNotFound 예외가 발생한다.
클래스 로더가 바이트 코드를 런타임 데이터 영역인 메소드 영역에 적재시킨다.
실행 엔진(Execution Engine)
런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행한다.
JVM 실행엔진은 바이트 코드로 된 파일을 기계어로 변환하는 역할을 담당한다.
이때 2가지 방법을 혼합하여 실행한다.
1. 인터프리터
2. JIT 컴파일러
인터프리터
바이트 코드 명령어를 하나씩 읽어서 해석하고 바로 실행한다.
자바의 특징 중 하나가 OS에 종속적이지 않다는 특징이 있었다. 각 플랫폼에 맞는 인터프리터가 바이트 코드를 실행하기 때문에 어떤 OS에서도 실행될 수 있다는 특징이 있다.
바이트 코드 명령어를 한 라인씩 읽기에 속도 문제가 발생한다. 바이트 코드도 결국 기계어로 변환이 돼야 하기에 타 언어에 비해 속도가 느려지기에 JIT 컴파일러를 같이 사용한다.
JIT 컴파일러(Just-In-Time Compiler)
인터프리터의 속도 문제를 해결하기 위해 만들어졌다.
인터프리터 방식으로 동작하다가 컴파일 임계치에 도달하면(자주 사용하면) 바이트 코드 전체를 컴파일해서 기계어로 변경하고, 이후에는 더 이상 인터프리팅을 하지 않고 기계어로 직접 실행하는 방식.
즉 자주 사용하면 바이트 코드 -> 기계어로 변경하여 즉시 실행하도록 만드는 것이다.
(기계어로 바꾸는 것 자체도 리소스가 소모되니 일정 임계치에 도달하면 변경한다)
변환된 코드는 캐시로 저장돼서 사용된다.(중복 컴파일 방지)
참고 자료
https://www.ibm.com/docs/ko/sdk-java-technology/8?topic=reference-jit-compiler
JIT 컴파일러
JIT (Just-In-Time) 컴파일러는 런타임 시 바이트 코드를 원시 시스템 코드로 컴파일하여 Java™ 애플리케이션의 성능을 향상시키는 런타임 환경의 컴포넌트입니다. Java 프로그램은 여러 다른 컴퓨터
www.ibm.com
☕ JVM 내부 구조 & 메모리 영역 💯 총정리
저번 포스팅에서는 JRE / JDK / JVM에 대해서 간략하게 알아보는 시간을 가졌다면, 이번 포스팅에서는 JVM의 내부 구조에 대해 좀 더 자세하게 알아보도록 할 예정이다. JVM(자바 가상 머신)은 자바 언
inpa.tistory.com
'JAVA' 카테고리의 다른 글
parseInt vs valueOf(Feat, 캐싱에 대한 이야기) (0) | 2024.07.04 |
---|---|
ArrayList는 어떻게 데이터를 관리할까?(디버깅 과정 포함) (0) | 2024.07.02 |
equals()와 hashcode() 정리(재정의) (2) | 2024.02.24 |
Class에 @Builder 붙이면 안되는 이유가 무엇일까? (0) | 2024.02.07 |