JVM
JVM은 Java Virtual Machine의 약자이다. 직역하면 자바를 실행하기 위한 가상 기계라는 뜻인데, 실제로 그 의미와 크게 다르지 않다.
우선 JAVA의 가장 큰 특징은 OS에 종속적이지 않다는 것이다. OS별로 각기 다르게 동작할 수 있는데, OS 종속되지 않는 다는 말은 OS위에 어떤 프로그램을 이용해 실행이 가능하다는 것이다.
즉, JVM이란 OS에 종속받지 않고 CPU가 JAVA를 인식하거나 실행할 수 있게 해주는 가상 머신이다.
JAVA 컴파일러
Java가 컴파일되는 과정을 이해하기 전에 컴퓨터가 명령어를 인식하는 과정부터 이해를 해야한다.
컴퓨터는 0과 1밖에 이해하지 못한다. 즉, 이러한 컴퓨터가 이해할 수 있는 명령어를 저급언어(Low-level Language)라고 부른다. 하지만 Java 코드는 알겠지만, 사람이 알아볼 수 있는 언어로 되어있다. 이러한 언어를 고급언어(High-level Language)라고 부른다.
따라서 CPU가 이해 할 수 있도록 고급언어를 저급언어로 변환하는 과정이 필요한데, 이를 컴파일이라고 하는 것이다.
그러나, C, C++ 같은 경우는 OS에 종속적이여서 컴파일을 하게 되면 바로 CPU가 이해 할 수 있는 명령어로 바뀌지만, 위에서 얘기했듯이 자바는 OS에 종속적이지 않아서 바로 CPU가 이해 할 수 있는 언어로 변환되지 않는다.
먼저 JVM이 이해 할 수 있는 언어로 변환을 한 이후에 CPU가 이해 할 수 있는 언어로 다시 변환하는 과정을 거친다.
이때, JVM이 코드를 인식할 수 있도록, 컴파일이 한 단계 진행되는데 컴파일이 진행되면 기존에 *.java 파일에서 *.class 파일로 변경된다. 그리고 해당 코드는 바이트 코드로 구성되어 있다.
실제로 JDK bin/ 폴더에 있는 javac.exe를 사용해서 *.java 파일을 *.class 파일로 변환 할 수 있다.
바이트 코드
그럼 바이트 코드란 무엇이냐, 바이트 코드는 VM에서 돌아가는 실행 프로그램을 위한 이진수로 표현된 코드이다.
이 부분에서 헷갈릴 수 있는 부분은 바이너리 코드와 바이트 코드의 차이이다.
VM이 아닌 기존 CPU가 이해 할 수 있는 코드가 바이너리 코드이고, 바이트 코드는 VM이 이해 할 수 있는 코드이다.
따라서, CPU가 이해하도록 하려면 다시 실시간 번역기, 혹은 JIT 컴파일러를 통해 바이트 코드를 바이너리 코드로 변환을 해주어야 한다.
JIT 컴파일러
JIT 컴파일러는 프로그램을 실제 실행하는 시점에 기계어(저급언어)로 번역하는 컴파일러이다.
기존의 자바는 인터프리터 방식으로 코드를 번역했는데, 인터프리터 방식으로 코드를 컴파일 하게 되면 너무 느리다.
간단한 예시로, 어느 번역가에게 요청 할 때마다 한 줄씩 번역해주는 것보다 책 전체를 번역해주고 보는 것이 읽는 사람의 입장에서는 더 빠를 수 밖에 없다. 이렇게 요청 할 때마다 한 줄씩 번역해주는 방식이 인터프리터 방식이고, 전체를 번역해주는 방식이 컴파일 방식이다.
그렇다면 JIT 컴파일러는 전체 코드를 한 번에 컴파일하는 것인가? 또 그렇지는 않다. JIT 컴파일러가 실제로 바이트 코드를 바이너리 코드로 변환하는 과정에는 많은 시간이 소요된다고 한다.
따라서, 한 번만 시행될 것 같은 코드는 인터프리터 방식이 더 좋기 때문에 인터프리터 방식을 사용하는데, 그럼 언제 JIT 컴파일러가 동작하는지 궁금할 것이다.
실제로 JIT 컴파일러는 인터프리터 방식으로 동작하다가, 사전 정의된 컴파일 임계 값을 활용해서 해당 임계 값을 넘어서게되면 해당 코드 부분을 컴파일한다. 그리고 컴파일 된 부분은 캐시에 저장되는데, 이후에 해당 코드를 불러오는 작업이 있다면 인터프리팅 하지 않고 해당 코드를 캐시에서 불러와서 빠른 속도로 실행 할 수 있다.
JVM의 구성요소
JVM은 위와 같은 구성을 가지고 있다.
Class Loader는 JVM 내로 클래스 파일을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다.
런타임시 동적으로 클래스를 로드하고 jar 파일 내 저장된 클래스들을 JVM 위에 탑재를 한다.
즉, Class Loader가 클래스를 처음으로 참조 할 때, 해당 클래스를 로드하고 링크하는 것이다.
위에 보면 Linking이 보이는데, 이 링킹이 여러 개의 코드와 데이터를 모아서 연결하여 메모리에 로드 될 수 있고 실행될 수 있는 한개의 파일로 만드는 작업을 해준다.
이 작업 이후 Runtime Data Area에 바이트 코드를 배치시킨다.
이어서 해당 작업들이 끝나고 나면 Execution에서 바이트 코드로 변환된 코드를 실행한다.
Runtime Data Area
Runtime Data Area는 실제로 위와 같은 구조를 가지고 있다.
각 영역들이 어떤 역할을 하는지 한 번 살펴보자.
1. Java Stack
- 메서드를 호출하는 과정에서 지역 변수, 매개 변수, 메서드 등 콜 스택에 잠시 올라오는 코드들 잠시동안 담기는 것들이 담기는 곳이다.
2. Native Method Stack
- Java는 JVM으로 돌아가다보니 SystemCall을 사용하는데에 있어서 원할하게 수행되지 않는 부분이 있다. 이 Native Method Stack은 이를 가능하게 해주는 Native Method를 호출하는 코드를 수행하기 위한 스택이다.
실제로, c 혹은 c++로 구성되어 있는 다른 언어를 수행한다.
- 그렇다고 항상 실행하려고 대기해서 담겨있는 것은 아니고, Java Stack에서 Java Code를 실행하다가 JNI(Java Native Interface)를 호출하게 되면 Java Stack에서 Native Method Stacks로 동적 링킹을 통해서 확장해서 실행하는 것이다.
3. Method Area
- 실제로 이 부분에 대해서 오해를 하고 있었는데, 단순히 클래스(static) 변수, 메서드 등이 올라가는 걸로만 알고 있었는데, 상당히 다른 것들도 많이 저장되고 있었다.
Method Area는 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다.
저장하는 것은 다음과 같은 것들이 저장된다.
1. Field Information (멤버 변수) : 멤버변수의 이름, 데이터 타입, 접근 제어자에 대한 정보
2. Method Information (메서드 정보) : 메서드의 이름, 리턴타입, 매개변수, 접근 제어자에 대한 정보
3. Type Information (타입 정보) : class인지 interface인지의 여부, Type의 속성, 전체이름, super 클래스의 전체 이름
다음과 같은 3개가 저장된다.
추가적으로 Constant Pool 또한 Method Area 영역에 포함되어 있다.
4. Heap Area
힙 영역은 다음과 같이 구성되어 있다.
Eden, Survivor, Old, Permanent 모두 가비지 콜렉션이 진행되는 것을 이해하기 위해서 알아둬야 할 영역들이다.
1. New/Young Generation : 생명 주기가 짧은 객체를 GC(Garbage Collection) 대상으로 하는 영역이다.
예외적으로 크기가 큰 객체의 경우에는 바로 Old로 들어가는 경우가 있는데, 일반적으로는 Eden 영역에 먼저 진입한다.
이 영역에서의 GC를 Minor GC라고 부른다.
2. Tenured Generation, Old : 생명 주기가 긴 객체를 GC 대상으로 하는 영역이다.
이 영역에서 발생하는 GC를 Major GC 라고 부른다. New/Young Genenaration 영역보다 큰 메모리 공간을 차지하고 있다.
New/Young Generation은 메모리 영역이 작아서 생각보다 자주 Minor GC가 발생하는데 Old 영역은 메모리 영역이 커서 Major GC가 자주 발생하지는 않는다. 하지만 한 번 발생하게 되면 Minor GC의 거의 10배정도에 달하는 시간이 걸리게 된다.
3. Permenent Generation : 생성된 객체들의 정보의 주소값이 저장된 공간이다. 클래스 로더에 의해 load 되는 Class, Method등에 대한 메타정보들이 저장되어 있다.
실제로 Reflection을 사용가능한 것이 해당 영역에 위와 같은 정보들이 저장되어 있기 때문에 가능한 것이다.
Reflection 포스팅은 다음에서 확인 가능하다.
Heap 영역에 대한 얘기를 할 때에는 Garbage Collector가 빠질 수 없는데, 해당 영역에 대한 내용도 상당히 길기 때문에, 후에 포스팅 해보도록 하겠다.
'Language > Java' 카테고리의 다른 글
Logger에서 hibernate SQL 로그 출력이 안될 경우 (0) | 2023.05.18 |
---|---|
가비지 콜렉션(Garbage Collection) (0) | 2023.04.22 |
enum의 Enum 상수 객체의 변수 사용과 생성 (0) | 2023.04.12 |
7-7 내부 클래스 (재업로드), 익명 클래스 (0) | 2023.03.29 |
12. Generic 제너릭 (0) | 2023.03.29 |