자바의 특징
- 객체지향 프로그래밍 언어이다.
- OS에 독립적이다.
- 가비지 컬렉터가 자동으로 메모리 관리를 해준다
- 분산 처리, 멀티쓰레드 기능을 지원한다.
JVM (JAVA Virtual Machine) 이란?
컴퓨터를 사용해 자바를 실행하기 위한 가상의 컴퓨터 환경
JVM을 통해서 바이트 코드를 해석해 OS에 전달함
자바 소스 코드의 실행 과정
hello.java (자바 파일) -> javac(자바 컴파일러) -> hello.class(클래스 파일) -> JVM -> 실행
JVM의 구성과 동작 방식
(class loader) + execution engine+ runtime data area + JNI로 구성되어 있다.
1. class Loader
Java 프로그램을 실행하면 Class Loader가 프로그램을 실행하는데 필요한
바이트 코드들을 Runtime Data Area에 동적으로 로드한다.
Class Loader는 계층형 구조로 이루어져 있다.
다음과 같은 순서대로 차례대로 클래스 로딩을 부모 계층에 위임해서 진행한다.
계층마다 클래스를 찾아 로딩하는 범위가 다르며
모든 계층에서 클래스를 찾지 못했을 경우 ClassNotFoundException이 발생한다.
- Bootstrap Class Loader : rt.jar에서 클래스 탐색, JVM을 실행하기 위해 필요한 클래스들과 Object, java.lang 클래스와 같은 Java API들을 로드한다.
- Extension Class Loader : 기본 Java API를 제외한 외부 라이브러리등의 확장 클래스들을 로드한다.
- System Class Loader : 애플리케이션의 클래스(개발자가 만든 클래스 ex Hello.class)를 로드한다.
- User-Defined Class Loader : 사용자가 직접 코드로 구현한 Class Loader, 사용자가 지정한 지정한 클래스를 로드한다.
클래스 로더는 추가로 다음과 같은 특징을 가진다.
- 부모 클래스 로더는 자식 클래스 로더가 찾은 클래스를 볼 수 없다.
- 상위 클래스 로더에의해 로드된 클래스는 하위 클래스 로더에 의해 다시 로드되지 않는다.
- 클래스 로드는 가능하지만 올라간 클래스를 내리는 Unload는 불가능하다.
클래스 로딩 과정은 loading -> Lingking -> initialization 순으로 진행된다.
메모리 로드 -> 검증 -> 초기화
- Loading : 클래스 파일을 읽어 JVM의 메모리에 로드한다.
- Linking : 로드한 클래스 파일을 검증한다.
- Verifying : 읽어들인 클래스 파일이 JVM 명세에 부합하는지 확인한다.
- Preparing : 클래스가 필요로하는 메모리를 할당한다.
- Resolving : 클래스 상수 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.(참조값 이름 -> 주소)
- Initializing : 클래스 변수들을 적절한 값으로 초기화한다.
2. 실행 엔진 (execution engine)
실행 엔진은 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 OS에 전달해 실행시킨다.
실행시에는 인터프리터, JIT 컴파일러 두 가지 방식을 혼용해서 사용한다.
- 인터프리터
- 바이트 코드 명령어를 하나씩 읽어 해석하고 바로 실행한다.
- 같은 명령어가 반복되어도 매번 해석하고 수행해서 실행 속도가 느리다.
- JIT(Just-In-Time) Compiler
- 인터프리터의 단점을 해결하기 위해 만든 컴파일러
- 주기적으로 반복되는 코드를 발견해 바이트 코드 전체를 한 번에 변환해 저장한 후 실행
- 반복되는 명령을 빠르게 실행할 수 있다.
- 명령어를 변환, 저장하는데도 비용이 소모되므로
인터프리터 방식으로 실행 중 일정 기준이 넘어가는 부분만 JIT 컴파일러로 실행
- 가비지 컬렉터 (Garbage Collector)
- Runtime Data Area에서 더 이상 활용하지 않는 부분의 메모리를 자동으로 회수해준다.
- 일반적으로 자동으로 실행되지만, 실행되는 시간은 정해져있지 않다.
3. runtime data area
JVM에서 runtime시에 사용하는 메모리 구역으로 5가지 구역으로 나뉜다.
- Method(static) : 정적 필드와 클래스 구조 저장
- Heap : 객체와 인스턴스 변수 저장
- Stack : 메소드 변수 저장
- Pc register : 현재 수행중인 명령어 주소 저장
- Native Method Stack Area : 기계어로 작성된 프로그램 실행(JIT 컴파일러로 컴파일된 부분, 자바 이외의 언어로 작성된 부분)
JAVA는 멀티 쓰레드 기능을 제공하는데 다음과 같은 방식으로 메모리가 관리된다.
쓰레드당 stack Area, PC Register, Native Method Stack Area가 할당되어 독립적으로 사용되고
Heap Area와 Method Area는 공통으로 사용한다.
Context Switching이 빠르고 메모리 자원을 아낄 수 있지만,
하나의 스레드의 문제가 다른 스레드에 영향을 줄 수 있고, 동시성 이슈가 발생할 수 있다.
멀티 프로세스 방식으로 메모리를 사용한다면 다음과 같은 방식으로 관리된다.
모든 메모리 영역이 독립적으로 사용된다.
Heap Area와 Method Area를 공용으로 사용해 생기는 문제점들을 예방할 수 있지만
그만큼 메모리 사용량이 커진다.
public class Hello {
static int n1 = 1;
public static void main(String[] args){
String name = "Kim";
int n2 = 5;
}
}
다음과 같은 코드를 실행시켰다고 가정해보자.
다음과 같은 모습으로 JVM은 Runtime Data Area에 메모리를 할당한다.
- Class Loader가 기본 클래스들을 Method Area에 로딩한다.
- Class Loader가 Hello.class를 로딩한 후 n1 변수를 1로 초기화한다.
- 인터프리터가 main 함수를 찾아 실행시킨다. (Stack Area에 메모리를 할당한다)
- main 함수 내부의 n2 변수를 초기화한다.
- main 함수 내부의 name 변수를 초기화한다
- 힙 영역에 String 클래스를 생성한다.
- String 클래스의 주소를 반환한다.
- main 함수 종료시 Stack Area의 main 함수 메모리를 Garbage Collector가 회수한다.
- 참조가 사라졌으므로 Heap 영역의 String 객체의 메모리도 회수한다.
- main 함수가 종료되었으므로 나머지 영역의 메모리를 모두 회수하고 JVM을 종료한다.
보통 멀티 스레드 환경에선 메소드 1개당 1개의 스레드를 할당한다.
그래서 메소드 안에서 선언된 지역변수를 다른 메소드 함수에서 사용할 수 없게 된다.
전역 변수 선언의 방식이 static인 이유도, static 영역에 변수를 저장해야 모든 스레드에서 전역으로 사용할 수 있기 때문이다.
스택과 메소드 영역에서 원시타입(Primitive Type) (ex int, boolean, char)은 값을 직접 저장하고
그 외의 나머지 모든 타입은 힙 영역에 저장한 후 주소만 저장해 참조한다.
따라서 하나의 객체가 여러 메소드에서 같이 사용된다면 (ex 싱글톤 방식)
힙 영역의 주소가 여러 스레드에서 공유되기 때문에 동시성 이슈가 발생할 수 있다.
PC Register는 쓰레드가 시작될 때 생성되며 현재 수행중인 JVM 명령어 주소를 저장한다.
JVM은 CPU에 직접 명령을 수행하는 것이 아닌 스택 프레임에 있는 Operand 스택 영역에서 명령어를 뽑아
pc register라는 별도의 메모리 공간에 저장한 후 CPU에 명령을 전달한다.
..라고 하는데 이 부분은 아직 완벽하게 이해하지 못했다.
나중에 어셈블리 언어, 컴퓨터 구조를 다 듣고 나면 이해가 되지 않을까..?
JRE(JAVA Runtime Envirorment) 는 뭔가요?
자바로 만들어진 프로그램을 실행하기 위해 필요한 라이브러리 (api) + JVM + class loader
JDK는 뭔가요?
자바를 개발하는데 필요한 라이브러리 + javac 등의 개발 도구 + JRE
'JAVA' 카테고리의 다른 글
JAVA의 정석 정독하기 #5 - 배열(Array) (0) | 2023.10.29 |
---|---|
JAVA의 정석 정독하기 #4 - 조건문과 반복문 (0) | 2023.10.22 |
JAVA의 정석 정독하기 #3 - 연산자 (0) | 2023.10.07 |
JAVA의 정석 정독하기 #2 - 변수 (0) | 2023.10.03 |
JAVA 문법 공부 #1 - 기본 문법 정리 (0) | 2023.03.05 |