이번 포스팅은 JVM에 대하여 포스팅해보려고 합니다. 부정확한 정보가 포함되었을 수 있습니다. 이해 부탁드립니다!
Java Virtual Machine의 약자로 OS에 종속 받지 않고 Java 애플리케이션을 실행시킬 수 있게 하는 가상의 컴퓨터입니다.. JVM은 OS에 의존적이지만 Java는 독립적이게 됩니다.
만약 JVM이 없다면 각 운영체제의 컴파일러를 사용해서 컴파일을 하게 되는데, 그렇게 되면 해당 운영체제(ex.윈도우)에서만 애플리케이션을 실행이 가능합니다. 다른 운영체제에서 실행을 하려면 다른 운영체제(ex.맥os)로 크로스 컴파일하여 그 운영체제에 맞는 실행 파일을 새롭게 만들어야 합니다.
그런데 자바 같은 경우는 각 운영체제에 설치되어 있는 JVM을 이용해 운영체제에 상관없이 실행 파일을 만들어 실행시킬 수 있습니다. .java
파일을 자바 컴파일러(javac.exe)가 컴파일하게 되면 .class
라는 파일이 생성됩니다. .class
파일의 경우 바이트코드(bytecode)로 이루어져 있는데 .class
파일의 바이트코드를 JVM이 읽어 각 운영체제에 맞게 실행시킬 수 있습니다.
바이트코드(byte code) : 사용자 언어인 자바와 기계어인 바이너리 코드(binary code)의 중간 언어이다. 바이트코드는 자바 코드를 배포하는 가장 작은 단위이기도 하며, 자바 컴파일러는 자바 소스 코드를 바이트 코드로 컴파일하여 JVM이 이해할 수 있게 한다.
클래스 로더(Class Loader)
가 컴파일된 자바 바이트코드를 런타임 데이터 영역(Runtime data area)
에 로드하고 실행 엔진(Execution Engine)이
자바 바이트코드를 실행시킵니다.[출처 : https://media.geeksforgeeks.org/wp-content/uploads/jvm-3.jpg]
자바는 컴파일 타임이 아닌 런 타임일 때 클래스 파일을 loading, linking, initializaion을 진행하여 메모리에 로딩합니다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더입니다.
부트스트랩 클래스 로더(Bootstrap ClassLoader)
확장 클래스 로더(Extension ClassLoader) :
시스템 클래스 로더(System ClassLoader) :
클래스 로더는 위임 계층 원리(Delegation Hierarchy Principal)를 따릅니다.
각 클래스 로더는 로드된 클래스들을 보관하는 namespace를 갖습니다. 클래스를 로드할 때 이미 로드된 클래스인지 확인을 위해 namespace에 보관된 Fully Qualified Class Name(이하 FQCN)을 기준으로 클래스를 찾습니다. FQCN이 같더라도 다른 클래스 로더가 로드한 클래스이면 다른 클래스로 간주됩니다.
JVM은 메모리 영역에 저장하는 것은 다음과 같습니다.
[출처:https://d2.naver.com/content/images/2015/06/helloworld-1230-3.png]
Verifying
Preparing
Resolving
- 심볼릭 레퍼런스(symbolic reference): 우리가 코드를 작성하면서 사용한 class, field, method의 이름을 지칭.
- 다이렉트 레퍼런스(direct reference): 실제 메모리의 주소값.
자바는 다양한 런 타임 데이터 영역을 정의하고 있고 실행될 동안 그 데이터를 사용합니다. 몇몇 영역(메모리 영역, 힙 영역, 런 타임 상수 풀)은 JVM에 의해 만들어지고 몇몇 영역(JVM 스택, PC Register, 네이티브 메서드 스택)은 실행 중인 스레드에 의해 만들어집니다. JVM에 의해 만들어진 메모리 영역은 jvm이 종료될 때까지 유지되고 스레드에 의해 만들어진 영역은 스레드가 종료되면 없어집니다.
[출처 : https://media.geeksforgeeks.org/wp-content/uploads/jvm-memory-2.jpg]
# Stack Frame
- 지역 변수 배열, 피연산자 스택, 현재 실행 중인 메서드가 속한 클래스의 런 타임 상수 풀에 대한 레퍼런스를 갖고 있다. 지역 변수 배열과, 피연산자 스택은 컴파일 시 정해지기 때문에 스택 프레임의 크기도 메서드에 따라 고정된다.
- 지역 변수 배열(Local Variable Array) : 0부터 시작하는 인덱스를 가진 배열로, 메서드에 전달된 파라미터들이 저장되며 그 이후는 메서드의 지역변수들이 저장된다.
- 피연산자 스택(Operand Stack) : 메서드의 실제 작업공간으로, 각 메서드는 피연산자 스택과 지역 변수 배열 사이에서 데이터를 교환하고 다른 메서드 호출 결과를 추가하거나 꺼낸다.
- 모든 JVM의 스레드에게 공유되며, JVM이 시작할 때 만들어진다.
가비지 컬렉터(GC)
가 메모리 사용의 효율성을 위해 사용되지 않는 개체들을 힙에서 비운다.# 런타임 상수 풀
클래스 파일 포맷에서 constant pool 테이블에 해당하는 영역이다. 메서드 영역에 포함되는 영역이긴 하지만 JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이다. 각 클래스와 인터페이스의 상수 뿐만 아니라 메서드와 필드에 대한 모든 레퍼런스까지 담고있는 테이블이다. 어떤 메서드나 필드를 참조할 때, JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행됩니다. 실행 엔진은 바이트 코드를 JVM 내부에서 기계가 실행할 수 있도록 네이티브 코드로 변경합니다. 이것을 JIT 컴파일러가 수행합니다. 실행 엔진에는 JIT compilier, Garbage Collector 등이 있다. 여기서는 특히 JIT 컴파일러에 대해 알아보고 GC같은 경우 따로 포스팅을 해야할 것 같습니다.
바이트코드를 네이티브 코드로 변환하는 방법은 수행 속도에 커다란 영향을 미친다. 컴파일러지만 프로그램이 수행될 때 적용된다.
인터프리터
는 바이트코드의 명령어를 하나씩 읽으면서 해석하고 실행합니다. 이것은 바이트코드를 변환하는 것 자체는 빠르나 인터프리팅한 결과의 실행은 느리다는 단점을 가지고 있습니다.JIT 컴파일러입
니다. JIT 컴파일러는 인터프리터 방식으로 실행하다 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경한다. 이후 해당 메서드를 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.오라클 핫스팟 VM은 핫 스팟 컴파일러라고 불리는 JIT 컴파일러를 사용합니다. 이것은 내부적으로 프로파일링을 통해 가장 컴파일이 필요한 부분을 찾아낸 다음 이 부분을 네이티브 코드로 컴파일하기 때문에 핫 스팟이라고 불립니다. 이것은 컴파일된 바이트코드라도 자주 사용되지 않는다면 캐시에서 네이티브 코드를 덜어내고 다시 인터프리터 모드로 작동합니다.
Java 8부터는 신경쓰지 않아도 된다.
https://www.geeksforgeeks.org/jvm-works-jvm-architecture/
https://www.geeksforgeeks.org/classloader-in-java/
https://d2.naver.com/helloworld/1230
https://doozi0316.tistory.com/entry/1%EC%A3%BC%EC%B0%A8-JVM%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%A4%ED%96%89%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B8%EA%B0%80
https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/
https://search.shopping.naver.com/book/catalog/32506068618?
자바의 신 (저자 이상민)