티스토리 뷰


JVM (Java Virtual Machine)

JVM은 자바 바이트 코드를 실행할 수 있는 주체입니다. JVM은 운영체제 위에서 동작하는 프로세스로 자바 코드를 컴파일해서 얻은 바이트 코드를 해당 운영체제가 이해할 수 있는 기계어로 바꿔 실행시켜주는 역할을 합니다. 자바는 JVM 위에서 동작하기 때문에 CPU나 운영체제(플랫폼)의 종류와 상관없이 JVM이 설치된 환경에서는 항상 동일하게 실행 가능합니다. 

 

JVM의 구성을 살펴보면 크게 4가지로 나눌 수 있습니다.

 

1. Class Loader

자바에서 소스를 작성하면 .java 파일이 생성됩니다. 이 파일은 컴파일을 통해 .class 파일(바이트 코드)로 변환됩니다. 자바 애플리케이션이 실행 중일 때(런타임 시) JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area에 생성된 클래스파일들을 동적으로 로드하는 역할을 하는 것이 Class Loader입니다.

 

- Bootstrap Class Loader

: JVM 시작 시 가장 최초로 실행되는 클래스 로더로, 자바 클래스를 로드하는 것이 아니라 자바 클래스를 로드할 수 있는 자바 자체의 클래스 로더와 최소한의 자바 클래스(java.lang.Object, Class, ClassLoader)만을 로드합니다. JAVA8 기준으로 ${JAVA_HOME}/jre/lib에 위치한 자바 런타임 코어 클래스를 로드합니다.

 

- Extension Class Loader (Java 9 이후부터는 Platform Class Loader)

: 확장 클래스 로더는 부트스트랩 클래스 로더를 부모로 가지며, 확장 자바 클래스들을 로드합니다. java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일을 로드하고, 이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext에 있는 클래스 파일을 로드합니다.

 

- Application Class Loader (Java 9 이후부터는 System Class Loader)

: 자바 프로그램 실행 시 Classpath에 있는 클래스 파일 또는 Jar에 속한 클래스들을 로드합니다.

 

- User-Defined Class Loader

: 애플리케이션 사용자가 직접 코드상에서 생성하여 사용하는 클래스 로더입니다.

 

Class Loader는 다음 세가지 원칙을 준수합니다.

1. Delegation Principle

: 위임 법칙은 클래스 로딩이 필요한 시점에 부모 클래스 로더 방향으로 클래스 로딩을 위임하는 것을 말합니다.

  (1) 새로운 클래스 로딩이 필요한 시점에 Runtime Data Area 내 Method Area에 클래스가 있는지 확인하고 있다면

       그 클래스를 사용합니다.

  (2) Method Area에 클래스가 없다면, 애플리케이션 클래스 로더에 클래스 로드를 요청합니다.

  (3) 애플리케이션 클래스 로더 -> 확장 클래스 로더 -> 부트스트랩 클래스 로더로 요청을 전달합니다.

  (4) 부트스트랩 클래스 로더는 부트스트랩 Classpath(JDK/JRE/LIB)에 해당 클래스가 있는지 확인하고 클래스가 존재하지 않는 경우 확장 클래스로더가 요청을 수행하도록 합니다.

  (5) 확장 클래스 로더는 확장 Classpath(JDK/JRE/LIB/EXT)에 해당 클래스가 있는지 확인하고 클래스가 존재하지 않는 경우 애플리케이션 클래스로더가 요청을 수행하도록 합니다.

  (6) 애플리케이션 클래스 로더는 애플리케이션 Classpath에 해당 클래스가 있는지 확인하고 클래스가 존재하지 않는

       경우 ClassNotFoundException을 발생시킵니다.

 

2. Visibility Principle

: 가시범위 원칙은 하위 클래스 로더의 경우 상위 클래스가 로드한 클래스를 알 수 있지만, 상위 클래스 로더는 하위 클래스 로더가 로드한 클래스를 알 수 없다는 것입니다.

 

3. Uniqueness Principle

: 유일성 원칙은 하위 클래스 로더는 상위 클래스에서 로드한 클래스를 다시 로드하지 않는다는 것입니다.

 

 

2. Execution Engine

Class Loader에 의해 메모리에 로드된 클래스(바이트 코드)들을 기계어로 변경해 명령어 단위로 실행하는 역할을 합니다. 명령어를 실행하는 방식은 인터프리터(interpreter) 방식과 JIT 컴파일러를 사용하는 방식이 있습니다. 두 방식에 대해서는 아래에서 다시 살펴보겠습니다.

 

 

3. Garbage Collector

Garbage Collector(GC)는 Heap 메모리 영역에 로드된 객체들 중에 참조되지 않는 객체들을 탐색 후 제거하는 역할을 합니다. GC가 역할을 수행하는 시간은 언제인지 보장되지 않습니다.

 

- Minor GC : New 영역에서 일어나는 GC

Runtime Data Area 영역 내 Heap Area에는 Eden, survivor1, survivor2 라는 영역이 있는데 이를 New 영역이라고 하고 다음과 같은 방식으로 GC가 동작합니다.

  • 최초에 객체가 생성될 때 Eden 영역에 생성됩니다.
  • Eden 영역이 꽉차면 Eden 영역 메모리를 그대로 survivor1 영역으로 복사한 뒤, survivor1 영역을 제외한 다른 영역의 객체를 제거합니다.
  • Eden 영역과 survivor1 영역이 모두 가득 차는 경우, Eden 영역과 survivor1 영역에 생성된 객체 중 참조되고 있는 객체만을 survivor2 영역으로 복사하고 survivor2 영역을 제외한 나머지 영역의 객체를 제거합니다.
  • 위 과정 중에 일정 횟수 이상 참조되고 있는 객체들은 Old 영역으로 이동시킵니다.

- Major GC (Full GC) : Old 영역에서 일어나는 GC

Heap Area 내 Old 영역의 모든 객체를 확인하며 참조되고 있지 않은 객체를 모아 한번에 제거합니다. 이 과정에서 Heap 영역 중간중간 빈 메모리 공간이 생기게 되고 이 부분을 없애기 위해 재구성이 됩니다. 메모리가 이동되고 있는데 다른 쓰레드에서 메모리에 접근하는 것은 옳지 않으므로 GC를 제외한 모든 쓰레드가 중지됩니다.

 

4. Runtime Data Area

JVM 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들이 로드되는 영역입니다.

 

- Method Area

: 클래스 필드 정보(클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보)와 메소드 정보(메소드의 이름, 리턴 타입, 파라미터, 접근 제어자), Type정보(Interface인지 class인지), Constant Pool(상수 풀 : 문자 상수, 타입, 필드, 객체 참조가 저장됨), static 변수, final class 변수등이 생성되는 영역입니다.

 

- Heap Area

: new 키워드로 생성되는 객체와 배열이 저장되는 영역입니다. 메소드 영역에 로드되어 있는 클래스만 생성이 되고 GC가 참조되지 않는 클래스를 확인하고 제거하는 영역입니다.

예를 들어, Person p = new Person(); 이라는 코드를 작성하면 Person p는 Stack 영역에 생성되고 new로 생성된 Person 인스턴스는 Heap 영역에 생성됩니다. 그리고 Stack 영역에 생성된 p의 값으로 Heap 영역에 생성된 Person 인스턴스의 주소값을 가지게 됩니다. 즉, 스택 영역에 생성된 p가 힙 영역에 생성된 객체를 참조하게 됩니다.

 

- Stack Area

: 지역 변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값등이 생성되는 영역입니다.

예를 들어, int a = 10; 이라는 코드를 작성했다면 정수 값을 할당할 수 있는 메모리 영역에 a라는 이름을 붙이고 10이라는 값을 저장하게 됩니다. 메소드를 호출할 때마다 개별적인 스택 영역이 생깁니다.

 

- PC register

: 쓰레드가 생성될 때마다 생성되는 영역으로 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역입니다.

 

- Native Method Stack

: 자바 외의 언어로 작성된 네이티브 코드를 위한 메모리 영역입니다. 주로 JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택입니다.

 

쓰레드가 생성되었다고 하면 Method Area와 Heap Area는 모든 쓰레드가 공유하고 Stack Area, PC register, Native Method Stack은 각각의 쓰레드마다 생성됩니다.

 


자바 컴파일 및 실행 과정

1. 자바 소스코드(.java)를 작성합니다.

2. 자바 컴파일러(javac)가 자바 소스코드를 컴파일하여 자바 바이트코드 파일(.class)로 변환합니다. 바이트 코드란, 고급 언어로 작성된 소스 코드를 가상머신(Java JVM 등)이 이해할 수 있는 중간 코드로 컴파일한 것으로 자바 코드를 배포하는 가장 작은 단위입니다.

3. 컴파일된 바이트 코드를 JVM의 클래스 로더에게 전달합니다.

4. 클래스 로더는 동적 로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크(검증 + 준비 + 분석)하여 Runtime Data Area에 로드합니다. 즉, JVM 메모리에 올립니다.

  •  클래스 로더 세부 동작
    • 로드: 클래스 파일을 가져와 JVM 메모리에 올립니다.
    • 검증: 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사합니다.
    • 준비: 클래스가 필요로 하는 메모리를 할당합니다. (필드, 메서드, 인터페이스 등)
    • 분석: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경합니다. 심볼릭 레퍼런스란, 참조하는 클래스의 특정 메모리 주소를 참조 관계로 구성하는 것이 아니라 참조하는 대상의 이름만을 지칭한 것을 의미합니다. Class 파일이 JVM에 올라가게 되면 심볼릭 레퍼런스는 그 이름에 맞는 객체의 주소를 찾아서 연결하는 작업을 수행합니다. 이후 분석 과정에서 실제 물리적인 주소로 대체되는 작업이 일어나는 것입니다.
    • 초기화: 클래스 변수들을 적절한 값으로 초기화 합니다. (static 필드)

5. 실행 엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행합니다.

  • 인터프리터: 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행합니다. 하나하나의 실행은 빠르나, 전체적인 실행은 느리다는 단점이 있습니다.
  • JIT(Just-In-Time) 컴파일러: 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 더 이상 해당 메서드를 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식입니다. 네이티브 코드는 캐시에 보관되기 때문에 한번 컴파일된 코드는 캐시에서 바로 꺼내서 사용하므로 빠릅니다. 하지만 JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 하나씩 인터프리팅하는 것보다 오래 걸리기 때문에 JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 얼마나 자주 호출되고 실행되는지 확인하여 일정 기준을 넘었을 때만 JIT 컴파일러를 사용하여 컴파일합니다.
💡컴파일하는 방법
1. 터미널에서 .java 파일이 있는 위치로 이동합니다.
2. javac 파일명.java 명령어를 수행합니다. → javac test.java
3. 폴더 안에 .class 파일이 생성됨을 확인할 수 있습니다.

💡실행하는 방법
1. 터미널에서 .java 파일이 있는 위치로 이동합니다.
2. java 파일명.java 명령어를 수행합니다. → java test.java

 


JDK와 JRE의 차이

JRE(Java Runtime Environment)

컴파일된 자바 프로그램을 실행시킬 수 있는 환경을 말합니다. JRE는 JVM이 자바 프로그램을 동작시킬 때 필요한 라이브러리 파일과 기타 파일들을 가지고 있습니다. JRE는 JVM의 실행 환경을 구현했다고 할 수 있으며, 자바 프로그램을 실행하기 위해서는 반드시 필요합니다.

 

JDK(Java Development Kit)

자바 개발자 도구로 JRE + 개발에 필요한 도구(javac, java 등)을 포함합니다.

 


참고

JVM: jeong-pro.tistory.com/148

Class Loader: leeyh0216.github.io/2020-04-18/java_class_loader

컴파일: gyoogle.dev/blog/computer-language/Java/%EC%BB%B4%ED%8C%8C%EC%9D%BC%20%EA%B3%BC%EC%A0%95.html

JDK, JRE: developerntraveler.tistory.com/49

'JAVA' 카테고리의 다른 글

[JAVA] 리스트 (List)  (0) 2021.04.15
[Java Study 04] 제어문  (0) 2021.04.14
[Java Study 03] 연산자  (0) 2021.04.13
[JAVA Study 02] 자바 데이터 타입, 변수 그리고 배열  (0) 2021.04.09
[JAVA ] 맵 (Map)  (0) 2021.04.04
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함