일단 먼저 이 세 개념에 대해 정리하고 시작하자.
자바 바이트코드(.class)를 실행할 수 있는 환경이다.
JVM에서는 바이트코드를 실행하고 가비지 컬렉션을 통해 메모리를 관리한다.
JVM은 OS, 하드웨어에 제한되지 않고 자바 바이트코드를 실행할 수 있도록 해준다.
(WORA "Write Once, Run Anywhere")
JVM과 JAVA 프로그램을 실행할 때 필요한 라이브러리(I/O, JDBC, etc...)를 포함한다.
JDK 11 부터는 JRE를 따로 제공하지 않고, JDK를 설치하도록 한다.
JRE와 자바로 개발할 때 사용되는 개발 도구(컴파일러(javac), 디버거(jdb))들이 포함되어 있다.
과거에 알고리즘 수업을 들을 당시, 과제를 0점 맞은 일이 있었다.
알고보니, 과제를 채점하는 교수님 컴퓨터에서 "컴파일 에러"가 난 이유였다.
과제의 조건에 GCC 컴파일러로 컴파일 해야한다는 조건이 있었고, 내 노트북 환경은 윈도우라 MSVC (Microsoft Visual C++) 컴파일러여서 에러가 났던 것이다.
이렇게, C언어에서는 플랫폼에따라 컴파일러가 달라진다. 컴파일러가 Binary Code를 운영체제마다 해석하는 방식이 다르기 때문이다.
하지만 자바 소스코드는 JVM을 이용하여 운영체제와 상호작용하기 때문에 플랫폼 독립적으로 실행이 가능하다.
javac(컴파일러)가 java 소스코드를 .class의 바이트코드로 컴파일한다.
그 후에는 어떤 환경에서든지 해당 운영체제에 맞게 설치된 JVM이 바이트코드를 실행한다.
- Class Loader : 바이트코드를 읽어 클래스 메타 데이터와 메소드 정보를 메모리의 Method Area에 적재한다.
- Method Area : 클래스의 메터데이터 (클래스명, 메소드명, 변수 타입 등)을 저장한다.
- Heap : 애플리케이션 내에서 생성되는 객체를 저장한다.
- PC Register : 현재 실행중인 명령어 주소를 저장한다.
- JVM Stack : 메소드 호출 시 스택프레임이 생성되어 메소드 호출에 대한 정보를 저장한다.
- Native Method Stack : 애플리케이션 내에서 C, C++ 등으로 작성된 외부 라이브러리를 호출할 때, 네이티브 코드에서 메소드 호출 정보를 저장한다. 이때, Native Method Interface를 이용한다.
- Interpretor : 메소드가 호출되면, 인터프리터가 바이트코드를 해석하고 실행한다.
- JIT 컴파일러 : 자주 호출되는 메소드는 JIT 컴파일러가 바이트코드를 네이티브 코드로 변환해두어 성능을 개선한다.
- GC : Heap에서 사용되지 않는 객체의 메모리를 해제하여 메모리 누수를 방지한다.
운영체제 수업을 잘 떠올려보자... "process" 는 메모리에 적재되어 실행되고 있는 프로그램이다. "자바 애플리케이션이 실행된다." 라는 말은 실제로 JVM이 프로세스가 된다. 라는 말과 같다. 자바 애플리케이션(우리가 코딩을 한 프로그램 ex) 스프링부트 앱..) 은 그냥 단순한 소스 코드로, 바이트코드로 변환된 후, JVM에서 이를 해석하며 처리될 뿐이다.
위 JVM 구조를 보면 JVM 내부에 Heap, PC register, Stack 같은 것들이 있는 것 볼 수 있다.
물리적으로 JVM 내부에 저것들이 존재하는 것인가? 하고 헷갈릴 수 있지만, 논리적 구조일 뿐이다.
프로세스는 자신만의 메모리 영역을 할당받는다. 그 안에 힙, 스택과 같은 메모리로 나누어지는 것이다. 그러므로 힙, 스택과 같은 것들은 메모리 공간을 추상화한 개념이다.
여기에서 다시 생각해보면, JVM은 프로세스가 되어 메모리에 적재되고, 위 그림에 있는 Method Area, Heap, PC Register, JVM Stack, Native Method Stack은 JVM 프로세스가 할당된 메모리 영역을 추상화한 공간이라는 것을 알 수 있다.
스레드는 자신만의 독립적인 스택 메모리를 갖는다. 힙은 프로세스 전체가 공유하는 메모리 공간이다. 따라서 모든 스레드가 같은 힙 공간을 공유한다. 따라서 GC가 힙을 관리하는 것이다.또한, 스레드는 각자의 PC Register를 갖는다.
PC Register는 스레드가 실행중인 명령어의 주소를 저장한다.
물리적으로는 메모리 내부에 있는 것이다. CPU의 PC와는 다른 것이다. CPU의 PC는 하드웨어 레지스터이다.
정리하면,
자바 프로그램을 실행하면 JVM이 프로세스가 된다.
Method Area, Heap은 모든 스레드가 공유하는 메모리 공간이고,
PC Register, JVM Stack, Native Method Stack은 각 스레드마다 독립적으로 소유하는 메모리 공간이다.
그림으로 한번 그려봤다.
찾아보니 누가 이미 예쁘게 그려놨다
스레드가 생성되는 시점은
스프링 부트 어플리케이션을 예로 들면,
실행 시, HTTP 요청 시, @Async 비동기 작업 수행 시, @Scheduled 스케쥴링 작업 시...
에 스레드가 생성된다.