CH01 JVM 이해하기
Last updated
Last updated
위 그림은 JDK, JRE, JVM 간의 관계를 나타내는 그림이다. JVM 은 밑에서 다루기로 하고 JRE와 JDK 관계만 보자.
JRE 는 Java Runtime Enviroment 의 약자다. 말 그대로 '실행환경'을 의미한다. 이것으로 개발을 할 수는 없다는 것이다. 실행을 해야하니까 JVM 을 포함하고 있다.
위 그림에서 확인할 수 있듯이 JRE 를 포함하고 있다. 그렇다면 JRE와 JDK 를 구분짓는 것들은 JRE 이 외의 것들이라는 것인데 대표적으로 컴파일러가 있다. 결론적으로 JDK는 JRE와 개발에 필요한 툴들을 의미한다.
JVM 의 역할을 한 문장으로 요약하자면 '.class 파일을 실행해준다.' 이다. 결론적으로 이 역할을 한다. JVM 이 내부적으로 어떻게 구성되어 있고 각 구성은 어떤 역할을 하는지 아래에정리했다.
아래가 JVM 구성도이다.(검정색 테두리) 그리고 그 아래 그림은 강의자료에 사용된 JVM 구조도이다. 개인적으로 바로 아래 그림이 전체라는 맥락에서의 JVM 을 볼 수 있어서 더 좋은 것 같긴한데, 강의자료의 이미지도 괜찮아서 둘 다 가져왔다.
일단 한 문장으로 JVM 의 흐름을 표현하자면 '바이트 코드를 메모리에 적재하여 이를 실행'한다고 표현할 수 있다.
.class 에서 바이트 코드를 읽고 메모리에 저장
로딩: 클래스를 읽어오는 과정
링크: 레퍼런스를 연결하는 과정
초기화: static 값들 초기화 및 변수에 할당
클래스 로더의 경우 강의에서 따로 챕터가 마련되어 있는데, 이에 대한 자세한 사항은 서브 챕터에서 정리하자.
메모리가 어떻게 구성되어 있고 각 부분들이 어떤 역할을 하는지에 대해서 이해하는 것이 핵심이다.
메소드: 클래스 수준의 정보(클래스 이름, 부모 클래스 이름, 메소드, 변수) 를 저장. 모든 스레드 공유.
힙: 객체를 저장. GC 동작 대상. 모든 스레드 공유.
스택: 스레드마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라고 부르는 블럭으로 쌓는다. 스레드 종료시 런타임 스택도 사라진다.
PC(Program Counter) 레지스터: 스레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 저장된다. CPU 스케줄링 매커니즘의 맥락에서 다음번 리소스 할당때 '전에 여기까지 했다' 라는 것을 표시하는 용도이다.
네이티브 메모리: 네이티브 메서드 호출에 사용되는 스택. 네이티브 코드 실행 시 필요한 메모리를 할당하는 영역. 네이티브 라이브러리, 네이티브 스택, Code Cache 등이 여기에 포함.
JVM(Java Virtual Machine)의 실행 엔진은 JVM 의 핵심 목표를 수행해주는 곳으로 자바 바이트코드를 실제로 실행하는 역할을 한다. 실행 엔진은 인터프리터, JIT 컴파일러, GC 로 구성되며 각 구성의 역할은 아래와 같다.
자바 바이트코드를 한 줄씩 읽어서 해석(바이트코드를 네이티브 코드로 해석)하고 실행. 해석한 네이티브 코드를 어딘가 저장해두는 것이 아니고 바로바로 네이티브 코드로 해석해서 이를 실행한다.
JIT 컴파일러의 JIT는 Just-In-Time 이라는 뜻이다. 실시간으로 컴파일하여 네이티브 코드로 변환하는 것을 의미한다. 인터프리터와 함께 사용되는데, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복되는 코드를 모두 네이티브 코드로 변환해둔다. 그 이후부터는 인터프리터의 해석 비용 없이 바로 실행 시킬 수 있게 된다. 참고로 미리 변환한 네이티브 코드는 코드 캐시(네이티브 메모리)에 저장된다.
요약하자면 인터프리터가 마주치는 반복되는 코드를 미리 네이티브 코드로 컴파일 하는 역할을 하는 것이 JIT 컴파일러이고 이렇게 미리 변환된 네이티브 코드는 인터프리터를 거치지 않고 직접 실행되어 성능상의 이점이 있다.
더 이상 참조되지 않는 객체를 모아서 정리한다. 자세한 사항은 따로 정리해둔 페이지를 참고하자. 위에 이미 썼지만 메모리 내 힙이 GC 의 활동 영역이다.
자바 애플리케이션에서 C, C++, 어셈블리로 작성된 함수를 사용할 수 있는 방법 제공
Native 키워드를 사용한 메소드 호출
네이티브 코드(Native code)와 바이너리 코드(Binary code)는 비슷한 의미를 가지는 용어로 사용될 수 있습니다. 두 용어는 기계어 형태의 코드를 의미하며, 컴퓨터가 직접 실행할 수 있는 형태의 코드를 말합니다.
네이티브 코드는 특정 플랫폼에 직접 실행되도록 컴파일된 코드를 말합니다. 예를 들어, C, C++ 등의 프로그래밍 언어로 작성된 코드를 해당 운영체제와 하드웨어 아키텍처에 맞게 컴파일하여 생성된 코드가 네이티브 코드입니다. 네이티브 코드는 특정 시스템 환경에 최적화되어 실행 속도가 빠르고, 직접 하드웨어 자원에 접근할 수 있습니다.
바이너리 코드는 0과 1로 구성된 이진 형태의 코드를 의미합니다. 컴퓨터가 이해할 수 있는 기계어 형태로 표현된 코드를 말하며, 프로그램의 실행 파일이나 라이브러리 파일 형태로 저장됩니다. 바이너리 코드는 컴퓨터에 의해 직접 실행되며, 일반적으로 실행 가능한 형태로 컴파일된 코드를 의미합니다.
따라서 네이티브 코드와 바이너리 코드는 비슷한 개념으로 사용되며, 기계어 형태의 코드를 의미하는 용어입니다. 다만, 상황에 따라서는 네이티브 코드가 특정 플랫폼에 직접 실행되도록 컴파일된 코드를 가리키는 용어로 사용되기도 합니다.
"네이티브 코드"와 "바이너리 코드"는 엄밀한 의미에서는 동의어는 아닙니다.
일반적으로 "네이티브 코드"는 특정 플랫폼에 직접 실행되도록 컴파일된 코드를 가리키는 용어입니다. 반면에 "바이너리 코드"는 0과 1로 구성된 이진 형태의 코드를 일반적으로 말합니다.
두 용어는 비슷한 의미로 사용되는 경우가 많기 때문에 혼동할 수 있습니다. 일부에서는 "네이티브 코드"와 "바이너리 코드"를 거의 동의어로 사용하는 경우도 있습니다. 하지만 엄격한 의미로 사용할 때에는 "네이티브 코드"는 특정 플랫폼에 최적화된 컴파일된 코드를, "바이너리 코드"는 이진 형태의 실행 가능한 코드를 의미합니다.
정리가 잘 되어있는 것 같아서 링크를 남긴다.