자바는 기본적으로 JVM이라는 가상머신에서 돌아가는 언어이다.
알다시피, 자바소스 파일(*.java)
은 자바 컴파일러(javaComplier)
에 의해서 바이트 코드 형태인 클래스 파일(*.class)
이 된다.
=> 이 클래스 파일은 Class Loader
가 읽어들이면서 JVM이 수행이 시작되는 것이다.
.java -> .class(bytecode) // javaComplier
Jre -> Jvm이 classloader로 .class를 loading
자, 이제 자바 컴파일러를 통해 클래스파일(ByteCode)이 JVM에 들어갈 때 과정을 보자.
JVM 내로 클래스 파일(byteCode)
을 로드하고, 링크를 통해 배치하는 작업을 수행한다. 런타임 시에 동적으로 클래스를 로드
한다.
JVM은 클래스에 대한 정보를 알지 못하기때문에 그 정보를 Class Loader가 Class 파일을 찾아 검사하고 메모리에 저장해두는 것이다.
JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다. 이 내용은 아래에서 자세하게 풀겠다.
Class Loader를 통해 JVM 내의 Runtime Data Area에 배치된 바이트 코드들을 명렁어 단위로 읽어서 실행한다.
Execution Engine은 이 바이트코드를 기계어로 변환하는 작업을 한다.
최초 JVM이 나왔을 당시에는 인터프리터 방식이었기때문에 속도가 느리다는 단점이 있었지만 정적 컴파일러 방식을 도입하여 이 점을 보완하였다. 이 방식이 JIT 컴파일러 방식
이다.
즉, Execution Engine안의 JIT 컴파일러는 바이트 코드를 처음에 인터프리터 방식을 사용하다가 반복적인 코드를 만나면 캐시에 담아두다가 정적 컴파일러 방식으로 변환하여 빠르게 실행한다.
JIT 컴파일러의 자세한 내용은 이 블로그에 참조하길 바란다. JIT 컴파일러이란?
Garbage Collector(GC)는 힙 메모리 영역에 생성된 객체들 중에서 참조되지 않은 객체들을 탐색 후 제거하는 역할을 한다. 이때, GC가 역할을 하는 시간은 언제인지 정확히 알 수 없다.
Runtime시 OS에게 할당받은 JVM은 Class Loader가 자바파일을 바이트코드로 만들고 메모리에 할당한다.
이 JVM 안에 바이트코드가 적재된 메모리 구조가 'Dynamic Runtime memory area'라고 한다. 줄여서 런타임 메모리 구조
라고 하겠다.
아래 그림이 구체적인 런타임 메모리 구조이다.
런타임 메모리구조는 세그멘테이션 구조로 5가지 영역으로 나뉘어져 있다.
바로, Method영역, Heap영역, Stack영역, PC 레지스터영역, Natvice Method stack 영역이다.
모든 스레드에게 공유
된다. 사용하는 Class파일의 ByteCode가 로드(by ClassLoader)되는 곳
으로 또, static이 붙은 것들은 모두 이 메모리에 할당
된다.
main 함수(스래드)에도 static이 붙어지는데, 자바가 실행되는데 main 메서드를 찾아서 바로 Metod영역에 할당하는데 이 값들은 JVM이 종료될때 까지 유지
된다.
모든 스레드에게 공유
된다. 동적으로 할당되는 변수들(레퍼런스 변수)이 이 메모리에 할당된다. 우리가 익히 아는 객체들이 여기에 속한다. GC(Garbage Collector)가 활동하는 영역이다.
만약, 객체가 참조되지 않는 변수가 있다면 GC가 없애버려준다.
각 스레드마다 하나씩 생성된다. Loop문이 들어왔을 때나 메서드 호출시, 메서드안에서 사용한 값들이 stack에 할당된다. 메서드안에서 사용한 값들이라 함은 지역변수, 매개변수, 리턴값, 복귀주소값
등을 말한다.
이 값들은 메서드가 호출되고 바로 stack영역에 저장되고 메서드 안 값들이 전부 호출되고 나면 바로 stack영역에서 pop하게 되어 사라지게 된다.(LIFO)
각 스레드마다 하나씩 생성된다. Program Counter
즉, 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. (*CPU의 레지스터와 역할이 비슷하다)
이것을 이용해서 쓰레드를 돌아가면서 수행할 수 있게 한다.
즉, 쓰레드가 어떤 부분을 무슨 명령으로 실행해야할지에 대한 기록을 하는 부분으로 현재 수행중인 JVM 명령(operationCode)의 주소값이 저장된다.
각 스레드마다 하나씩 생성된다. 자바 외 언어(C/C++)로 작성된 네이티브 메서드를 위한 메모리 영역이다. 이것도 stack이다.
JNI(Java Native Interface)라는 표준 규약을 제공한다.
class A {
int a;
String str;
}
...
public static void main(String[] args) {
A sample = new A(); // A 인스턴스 생성 => Heap영역에 들어갈까? Stack영역에 들어갈까?
}
이렇게 main 메소드에서 A의 인스턴스를 생성하게 되면
Stack영역에는 지역변수인 sample과 sample의 내용이 저장된 주소값이 저장되고
Heap영역에는 sample 안에 있는 인스턴스 변수인 int a와 String str이 저장되게 된다.
즉 new 로 생성된 객체의 내용은 Heap영역에 저장되고 참조형 변수인 sample은 main함수 안에 있는 지역변수 이므로 Stack영역에 저장되는 것이다.
쓰레드가 생성되었을 때 기준으로
1,2번인 메소드 영역과 힙 영역을 모든 쓰레드가 공유
하고,
3,4,5번인 스택 영역과 PC 레지스터, Native method stack은 각각의 쓰레드마다 생성되고 공유되지 않는다.
https://medium.com/platform-engineer/understanding-jvm-architecture-22c0ddf09722
https://pienguin.tistory.com/entry/JAVA-%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95-%EB%B0%8F-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0
https://jeong-pro.tistory.com/148 [기본기를 쌓는 정아마추어 코딩블로그]