JDK, JRE, JVM 그리고 메모리 구조
JDK
JDK는 자바 개발도구(Java Development Kit)의 약자이다.
JDK는 JRE 에서 개발을 위해 필요한 도구(javac, java, visualVM 등)들을 포함한다.
JRE
JRE는 자바 실행환경(Java Runtime Environment)의 약자이다.
JRE는 JVM 이 자바 프로그램을 동작시킬 때 필요한 라이브러리 파일들과 기타 파일들을 가지고 있다. JRE는 JVM의 실행환경을 구현했다고 할 수 있다.
JVM
위키에서는 JVM을 Java Byte Code를 실행하는 주체라고 정의하고 있다.
바이트 코드를 실행하는 주체의 의미는, 자바 소스코드로부터 만들어지는 자바 바이너리 파일(.class)을 실행할 수 있다는 뜻이다.
컴파일된 바이너리 코드(.class)는 어떤 JVM에서도 동작시킬 수 있게 된다.
출처: https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EA%B0%80%EC%83%81_%EB%A8%B8%EC%8B%A0
JVM의 역할
- 바이너리 코드를 읽는다.
- 바이너리 코드를 검증한다.
- 바이너리 코드를 실행한다.
- 실행환경(Runtime Environment)의 규격을 제공한다. (필요한 라이브러리 및 기타파일)
JVM의 특징
1) 스택 기반의 가상 머신
- 대표적인 컴퓨터 아키텍처인 인텔 x86 아키텍처나 ARM 아키텍처와 같은 하드웨어가 레지스터 기반으로 동작하는 데 비해 JVM은 스택 기반으로 동작한다.
2) 심볼릭 레퍼런스
- 기본 자료형(primitive data type)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스를 통해 참조한다.
3) 가비지 컬렉션(garbage collection)
- 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고 가비지 컬렉션에 의해 자동으로 파괴된다.
4) 기본 자료형을 명확하게 정의하여 플랫폼 독립성 보장
- C/C++ 등의 전통적인 언어는 플랫폼에 따라 int 형의 크기가 변한다. JVM은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장한다.
5) 네트워크 바이트 오더(network byte order)
- 자바 클래스 파일은 네트워크 바이트 오더를 사용한다. 인텔 x86 아키텍처가 사용하는 리틀 엔디안이나, RISC 계열 아키텍처가 주로 사용하는 빅 엔디안 사이에서 플랫폼 독립성을 유지하려면 고정된 바이트 오더를 유지해야 하므로 네트워크 전송 시에 사용하는 바이트 오더인 네트워크 바이트 오더를 사용한다. 네트워크 바이트 오더는 빅 엔디안이다.
=> 많은 자바 개발자들이 알고 있는 메모리 할당과 해제를 알아서 수행해주는 가비지 컬렉터와 플램폼에 의존성 즉, JVM을 구성할 수 어느 플램폼에서든지 이식할 수 있다는 특징은 JVM의 가장 큰 장점일 것이다. 개인적으로 네트워크 프로그래밍을 하는 개발자라면, 자바언어는 빅 엔디안을 따른다는 것 쯤은 알고 있어야한다.
JVM의 수행과정
자바 컴파일러에 의해 컴파일된 클래스 파일을 JVM의 클래스로더에 의해 Runtime Data Areas(런타임 데이터 영역)에 적재하고, 실행엔진이 자바 바이트 코드를 실행시킨다.
여기서 흔히 알고 있는 특징인 자바 바이트 코드는 컴파일 시점이 아닌, Excution Engine에 의해 런타임(실행)시점에 실행된다는 점이 바로 위의 과정을 통해 알 수 있다.
Runtime Data Areas
런타임 데이터 영역은 JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역, 우리가 알고 있는 자바의 메모리 영역이다.
런타임 데이터 영역은 6개의 영역으로 나누어 지는데 , PC Register와 Stack, Native Method stack 은 쓰레드마다 차지 하는 영역이며 Heap, Method Area, Runtime Constant Pool 영역은 모든 쓰레드가 공유하는 영역이다.
1) PC 레지스터
- PC(Program Counter) 레지스터는 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 생성된다. PC 레지스터는 현재 수행 중인 JVM 명령의 주소를 갖는다.
2) JVM Stack
- JVM Stack은 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 생성된다. 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택으로, JVM은 오직 JVM 스택에 스택 프레임을 추가하고(push) 제거하는(pop) 동작만 수행한다. 예외 발생 시 printStackTrace() 등의 메서드로 보여주는 Stack Trace의 각 라인은 하나의 스택 프레임을 표현한다.
3) Native Method Stack
- 자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다. 즉, JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C 스택이나 C++ 스택이 생성된다.
4) Method Area (= Class Area, Static Area)
- 메서드 영역은 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다. JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다. 메서드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다. 메서드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.
- 위의 말이 어려운데 한 마디로 정의하면 컴파일된 클래스파일의 자바 바이트 코드의 모든 데이터가 올라가는 영역이다. 포함되는 범위는 전역 변수, 메서드 정보, static 자료형이 붙은 필드와 메소드 등등 이며 모든 쓰레드가 공유하는 Method area에 올라가기 때문에 어느 쓰레드에서든지 클래스 내의 메소드 혹은 전역 변수 값에 접근할 수 있는 것이다.
5) Runtime Constant Pool
- 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다. 메서드 영역에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다. 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다. 즉, 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
6) Heap
- 인스턴스 또는 객체를 저장하는 공간으로 가비지 컬렉션 대상이다. JVM 성능 등의 이슈에서 가장 많이 언급되는 공간이다. 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더의 재량이다.
Class Loader
- 위에서도 언급했지만, 자바는 컴파일 시점이 아닌 런타임 시점에 클래스를 처음으로 참조하며 해당 클래스를 로드하고 링크하는 동적 로드의 특성을 갖고 있다. 이 동적로드를 담당하는 부분이 JVM의 클래스 로더이다.
- 클래스로더의 특징은 다음과 같다.
1) 계층 구조: 클래스 로더끼리 부모-자식 관계를 이루어 계층 구조로 생성된다. 최상위 클래스 로더는 부트스트랩 클래스 로더(Bootstrap Class Loader)이다.
2) 위임 모델: 계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 구조로 동작한다. 클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고, 없다면 로드를 요청받은 클래스 로더가 클래스를 로드한다.
3) 가시성(visibility) 제한: 하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없다.
4) 언로드 불가: 클래스 로더는 클래스를 로드할 수는 있지만 언로드할 수는 없다. 언로드 대신, 현재 클래스 로더를 삭제하고 아예 새로운 클래스 로더를 생성하는 방법을 사용할 수 있다.
Class Loader 우선순위
Bootstrap > Extention > System
클래스로더가 로드를 요청받으면, 클래스 로더 캐시 상위 클래스 로더 자기 자신 순서로 클래스가 있는지 확인한다
부트 스트랩 클래스로더까지 확인해도 없으면 요청받은 클래스 로더가 파일 시스템에서 해당 클래스를 찾는다.
1) BootStrap ClassLoader
- JVM이 실행될때 가장 먼저 실행되는 클래스 로더로, 자바 실행에 필요한 기본적은 클래스를 로딩한다.
- 다른 클래스로더와 달리 네이티브 코드로 구현되어 있다.
2) Extention ClassLoader
- 추가로 로딩되는 클래스로더로 별도로 클래스패스에 설정되어 있지 않아도 로딩된다.
- 다양한 보안 확장 기능을 여기서 로드한다.
3) System ClassLoader
- ClassPath에 정의 되어 있거나 JVM옵션에서 -cp, -classpath에 지정된 클래스들이 로딩된다.
- 시스템 클래스로더는 애플리케이션의 클래스들을 로드한다고 할 수 있다.
- 즉 사용자가 지정한 $CLASSPATH내의 클래스가 로드된다.
4) User-Defined Class Loader
- 애플리케이션 사용자가 직접 코드 상에서 사용하는 클래스 로더이다.
- 일반적으로 Jar 혹은 WebApp의 경우 War로 압축된 ClassPath의 Binary Code를 사용자 정의 클래스 로더가 로드한다.
클래스 로더가 로드하지 않은 클래스를 찾으면 다음과 같은 과정을 통해 로드하고 링크하며 초기화한다.
1) 로드: 찾은 클래스 파일을 Runtime Data Area에 로드한다.
2) 검증: 읽어 들인 클래스가 제대로 구성되어 있는지 검사한다. 클래스 로드 전 과정 중 가장 시간이 오래 걸리며 까다로운 검증을 거친다.
3) 준비: 클래스가 필요로 하는 메모리를 할당하고, 클래스에 정의된 필드, 메소드, 인터페이스들을 나타내는 데이터 구조를 준비한다.
4) 분석: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다. ( 심볼릭 레퍼런스는 메모리 번지의 참조를 의미하는 것이 아니라 이름에 의한 참조를 의미)
5) 초기화: 클래스 변수들을 적절한 값으로 초기화한다.
참고 :
- https://d2.naver.com/helloworld/1230
- https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EA%B0%80%EC%83%81_%EB%A8%B8%EC%8B%A0
- https://docs.oracle.com/javase/8/docs/technotes/guides/vm/index.html
- http://yoyojyv.tistory.com/48