6 min read

<Java> JVM 이해하기

해당 글은 백기선님의 <더 자바, 코드를 조작하는 다양한 방법> 강좌를 기반으로 작성되었습니다.

JVM,JRE,JDK 구별하기

JVM(Java Virtual Machine)이란?

  • 자바 바이트 코드를 실행하는 역할을 수행한다.
  • 즉, 바이트코드를 해당 OS에 특화된 언어로 변환하여 실행한다.
  • 바이트 코드를 실행하는 표준이자 특정 벤더(제공사 : oracle,aws 등등)가 구현 가능하다.

JRE(Java Runtime Enviroment)란?

  • 간단하게 보면 JVM + 핵심 Library로 구성된 자바 어플리케이션을 실행 할 수 있도록 구성된 배포판이라 할 수 있다.(JVM의 실행 환경을 구축함)
  • 쉽게 말해, JVM만 배포하지않고 JRE 기준으로 배포한다.
  • 개발 관련 도구를 포함하지는 않는다.

JDK(Java Development Kit)란?

  • JRE + 개발에 필요한 툴로 구성됨
  • 오라클은 자바 11부터 JDK만 제공하여서, JRE를 따로 제공하지 않는다.(서서히 둘의 구분이 사라질 예정..)
  • 소스코드를 작성할때 사용하는 자바 언어는 플랫폼에 독립적
  • 그래서 Write Once Run Anywhere

JDK 구조 파헤치기

클래스 로더 시스템

  • 컴파일 후에 생성되는 .class 파일에서 바이트 코드를 읽어오고 메모리에 분산 저장시키는 역할 수행
  • 로딩 : 클래스 읽어오는 과정
  • 링크 : 레퍼런스를 연결하는 과정
  • 초기화 : static 값들 초기화 및 변수에 할당

메모리(Runtime Data Area)

  • 메모리 영역은 Runtime Data Area라고도 불리며, 이는 JVM이 프로그램을 수행하기 위해 OS로부터 할당받는 메모리이다.
  • Method 영역에는 클래스 수준의 정보(클래스 이름, 부모 클래스 이름, 메소드, 변수 등) 저장하며, Runtime Constant Pool이 존재한다.
  • 논리적으로는 Method 영역은 Heap 영역에 포함된다. 동시에 GC의 대상이 되는 영역이다.
  • Heap 영역은 객체(동적으로 생성되는 인스턴스)를 저장한다.
  • Method 영역과 Heap 영역은 쓰레드 간의 공유가 가능한 영역이다.
  • 해당 두개의 영역 제외한 모든 영역은 쓰레드마다 존재하는 영역, 즉 공유 불가 자원이다.
  • 스택 영역(JVM Stack)에는 쓰레드마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라는 부르는 블럭으로 쌓는다. 해당 메소드가 호출이 종료되면 스택에서 제거하며, 쓰레드가 종료되면 런타임 스택도 사라진다.
  • PC Register 영역 : 쓰레드마다 쓰레드 내 현재 실행할 스택 프레임을 가르키는 포인터가 생성된다
  • Native method stack : Java 언어 외로 작성된 네이티브 언어를 위한 스택이 존재

실행 엔진(Execution Engine)

  • 실행 엔진은 클래스를 실행시키는 역할을 수행하며, 클래스 로더가 바이트 코드를 런타임 데이터 영역에 배치시키고 이것이 실행엔진에 의해서 실행된다.
  • 자바 바이트 코드를 기계어로 바꾸는 역할을 수행한다.
  • 인터프리터(Interpreter) : 바이트 코드를 한줄 씩 기계어로 변경해서 느리다는 단점을 가진다.
  • JIT(Just In Time) 컴파일러 : 인터프리터의 효율을 높이기 위해서, 인터프러터가 반복되는 코드를 발견하면 JIT컴파일러로 반복되는 바이트 코드를 모두 네이티브 코드를 바꾼다. 그 다음부터 인퍼프리터는 네이티브 코드로 컴파일된 코드를 바로 변환한다.바이트 코드를 네이티브로 바꾸는 시간이 오래걸리는 단점이 있지만 해당 네이티브 코드는 캐시에 보관하기 때문에 다음 번에 재수행 시 속도가 상당히 빠르다.그래서 JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드의 호출 빈도를 파악하여 일정 수준 이상인 경우 컴파일한다.
  • GC(Garbage Collector) : 더 이상 참조되지 않는 객체를 모아서 정리한다.

Native Method Interface(JNI)

  • 자바 어플리케이션에서 C,C⁺⁺,어셈블러로 작성된 함수를 사용 할 수 있는 방법 제공한다.
  • Native 키워드를 사용해서 메소드를 호출한다.

Class Loader 파헤치기

  • 로딩,링크,초기화 순으로 진행된다.

Loading

  • 클래스 로더가 .class 파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들고 메소드 영역에 저장한다.
  • 로딩이 끝나면 해당 클래스 타입의 Class객체를 Heap영역에 저장
  • 이 과정은 부트 스트랩 클래스로더 -> 익스텐션(플랫폼) 클래스로더 -> 애플리케이션 클래스 로더 순으로 클래스를 찾으며 최종적으로 찾지 못할 시 Class Not Found Exception이 발생한다.
  • 아래 코드와 결과를 보면 클래스 로더의 계층구조를 알 수있다.
public class App 
{
    public static void main( String[] args )
    {
        ClassLoader classLoader = App.class.getClassLoader();
        System.out.println(classLoader);
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());//native-code로 구현되어서 null 출력
    }
}

Linking

  • Verify, prepare, Resolve 순의 세 단계로 이루어져 있다.
  • Verify : .class 파일 형식이 유효한지 체크한다.
  • Prepare : 클래스 변수(static 변수)와 기본값에 필요한 메모리 준비한다.
  • Resolve : 선택가능한 단계로 Symbolic Memory Reference를 메소드 영역에 있는 실제 레퍼런스로 교체한다.

Initialization

  • Static 변수의 값을 할당한다.(Static 블럭이 있다면 이 단계에 실행된다)