면접준비 - Java JVM(1)
오타, 지적 환영입니다.
JVM 이란 무엇인가?
- JVM은 JAVA Virtual Machine의 약자. 자바 가상 머신이란 뜻이다. 가상 머신은 프로그램 실행 등을 위해 물리적 머신과 비슷하게 소프트웨어로 구현한 것이다.
- JVM의 목적은 클래스 로더를 통해 자바API와 함께 자바 애플리케이션을 실행하는 것.
- JVM은 OS와 JAVA사이에 중개자 역할을 한다. 중간에서 자바 바이트코드를 읽는다.
- JVM은 따라서 OS에 구애받지 않고 자바 애플케이션을 실행할 수 있고, 그 외에 GC, 메모리 관리의 역할도 한다.
- JVM은 스택 기반의 가상머신이다. ARM 아키텍쳐 같은 하드웨어는 레지스터 기반으로 동작하는데 JVM은 스택으로 동작한다고 한다. 왜 그럴까?
JVM은 왜 스택기반 가상머신일까
스택기반 VM의 장단점.
장점
- 하드웨어(레지스터, CPU)에 직접적으로 다루지 않아 하드웨어에 덜 의존적이다.
- 피연산자들은 TOP에 존재하여 피연산자의 메모리 주소를 사용할 필요가 없어서 명령어의 길이가 짧아짐.
단점
- 명령어의 수가 많아짐. POP, PUSH 이런 명령어를 자주 사용해야하므로.
- 스택을 사용하면 오버헤드가 존재한다.
- 명령어 최적화가 힘듦
레지스터기반 VM의 장단점
장점
- 명령어 수가 적다.
- 오버헤드의 부담이 상대적으로 덜하다
- 명령어 최적화가 가능. (연산결과를 레지스터에 넣어서 사용이 가능)
단점
- 피연산자의 메모리 주소를 적어줘야 하기에 명령어의 크기가 커짐.
결론
개인적인 생각으로 JVM의 목적은 하드웨어에 종속되지 않는것이므로 스택기반의 VM을 사용하는것이 어찌보면 당연할 수 있다. 레지스터 기반으로는 레지스터 갯수, 사이즈 등에 의존할 수 밖에 없기 때문이다.
JVM을 알아둬야 하는 이유는 메모리를 잘 관리하기 위해서는 메모리를 직접적으로 관리하는 JVM에 대해 알아야한다.
자바프로그램 실행과정
- JVM은 OS로부터 자바프로그램이 할당받을 메모리를 요청하여 받는다. JVM은 용도에 따라 이 메모리를 나눠 관리한다.
- 자바 컴파일러가 자바 소스코드를 자바 바이트 코드로 변환함 (.java -> .class)
- Class Loader가 .class파일을 JVM로 로딩한다.
- 로딩된 .class파일은 Execution engine에 의해 해석된다.
- 해석된 바이트 코드들은 Runtime Data Area에 배치되어 실질적인 수행이 이루어짐.
Class Loader(클래스 로더)
애플리케이션 실행중 필요할때마다 동적으로 Java 클래스들을 로드한다. 클래스 로더에는 로딩, 링크, 초기화 단계로 나뉘어져 있다.
로딩
자바 바이트 코드(.class)파일을 메소드 영역에 저장하고, 각 자바 바이트 코드 JVM에 의해 메모리 영역에 다음정보를 저장한다.
- 로드된 클래스, 로드된 클래스의 부모 클래스 정보
- 클래스 파일과 Class, Interface, Enum등 정보
- 변수나 메소드 정보
링크
- 검증 : 읽어들인 클래스와 자바 언어 명세 및 JVM 명세된 대로 구성되어있는지 검증.
- 준비 : 클래스가 필요로 하는 메모리를 할당하고, 클래스들에서 정의된 필드, 메소드 등을 나타내는 데이터구조를 준비
- 분석 : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체
초기화
- 초기화 : 클래스 변수들을 적정한 값으로 초기화.
static
필드들이 설정된 값으로 초기화
종류
클래스 로더에는 종류가 있다.
- 부트스트랩 클래스 로더 : JVM 시작 시 가장 최초로 실행되는 클래스 로더. 자바 클래스를 로드할 수 있는 자바 자체의 클래스 로더와 최소한의 클래스(java.lang.Object, Class, ClassLoader)만을 로드.
- 확장 클래스 로더 : java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일들을 로드.
- 시스템 클래스 로더 : 개발자가 지정한 Classpath에 있는 클래스 파일 혹은 jar에 속한 클래스들을 로드.
클래스 로더가 지켜야할 세 가지 원칙
- 위임원칙 : 리소스를 찾기 위해 요청을 받았을때 상위 클래스 로더에게 책임을 위임. (부트스트랩 클래스로더 -> 확장 클래스로더 -> 시스템 클래스로더)
- 가시 범위 원칙 : 하위 클래스 로더는 상위 클래스 로더가 로드한 클래스를 볼 수 있지만, 상위 클래스 로더는 하위 클래스 로더가 로드한 클래스를 볼 수 없다.
- 유일성 원칙 : 하위 클래스 로더가 상위 클래스 로더에게 로드한 클래스를 다시 로드하지 않아야 한다는 원칙
동적 클래스 로딩
런타임에 동적으로 클래스를 로딩한다는 것은 JVM이 미리 모든 클래스에 대한 정보를 메소드 영역에 로딩하지 않는다는 것을 의미.
종류와 과정 실습 코드는 링크를 참고.
Execution Engine(실행 엔진)
클래스를 실행 시키는 역할. 클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키고, 실행엔진에 의해서 실행됨. 바이트 코드 자체는 기계어가 아니기에 실행 엔진이 바이트 코드를 JVM내부에서 기계가 알 수 있는 형태로 변경. 두 가지 방식이 있음.
인터프리터 방식
인터프리터 방식은 원시 코드를 바로 실행하는 프로그램 또는 환경을 말한다. 바로 실행한다는 것은 컴파일러와 달리 명령어를 한번에 한 줄씩 읽어서 실행하는 프로그램이다. 인터프리터는 고급 명령어들을 중간 형태로 번역한 다음 그대로 실행해버린다는 특징을 가지고 있다. 하지만 컴파일된 프로그램들에 비해 일반적으로 느리지만 컴파일을 거칠 필요가 없어서 프로그램의 크기가 커지면 커질수록 컴파일된 프로그램보다 빠르게 동작할 수 있다는 장점이 있다.
JIT(Just In Time)
인터프리터 방식의 단점을 보완하기 위해 나타난 JIT 컴파일러이다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 바꿔버려 인터프리팅을 하지 않고 이후 네이티브 코드를 직접 실행한다. 네이티브 코드는 캐시에 보관하기에 한 번 컴파일된 코드는 빠르게 수행이 가능하다.
여기서 적절한 시점이란 프로그램 실행 흐름 단위를 실시간으로 추적하고 탐색하는 Tracing JIT도 있고, 메소드들이 얼마나 자주 수행되는지 추적하여 일정 수준이 넘으면 컴파일하는 경우도 있다.
JVM Runtime Data Area
JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 곳.
먼저 스레드가 각자 갖고 있는 3가지 영역에 대해 알아둘 필요가 있다.
1. PC Register(스레드 별)
스레드가 생성될 때마다 생성되는 공간으로 스레드마다 하나씩 존재한다. 스레드가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행중인 JVM 메모리를 갖음.
2. Stack Area(스레드 별)
Stack Frame을 저장하여 메소드 안에서 사용되는 값들(지역 변수)들을 임시로 저장하고, 호출된 메소드의 매개변수나 리턴 값 등을 임시로 저장.
3. Native Method Stack(스레드 별)
자바 프로그램이 컴파일되어 생성되는 바이트 코드가 아닌 기계어로 작성된 프로그램을 실행 시키는 영역. 자바 이외의 언어(C, C++ 등)로 작성된 코드를 실행할 때 할당됨.
4. Method Area(Class area, Static area)
클래스 정보를 처음 메모리 공간에 올릴 때 초기화 되는 대상들을 저장하기 위한 공간. 이 공간에는 Runtime Constant Pool이라는 별도의 관리 영역이 존재. 이는 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 수행.
- 멤버 변수의 이름, 데이터 타입, 접근제어자 정보
- 메소드 리턴타입, 이름, 매개변수, 접근제어자 정보
- class인지 interface인지 여부 저장
가비지컬렉션 관리 대상에 포함.
5. Heap(힙 영역)
객체를 저장하는 가상 메모리 공간. new
연산자로 생성된 객체와 배열 등을 저장.
자바 8버전 이후에는 Permenant Generation 영역이 사라짐.
GC에 대해서는 다음 포스팅에서