JVM은 자바 바이트코드를 실행하는 가상머신으로,
운영체제의 독립성을 보장하는 핵심 실행 엔진이다.

이번 포스트는 앞선 JDK, JRE에 대해 정리하였던 내용의 연장선으로
JVM의 개념과 내부 구조 및 기능 동작 방식에 대하여 알아보도록 한다.


JVM: Java Virtual Machine의 정의 / 주 기능

JVM(Java Virtual Machine)이란 해당 포스트 최상단에 작성한 것처럼,
바이트코드를 실행하는 가상머신이다.

기존에 알고있던 경험을 기반으로한 가상머신에 대한 개념은

Operating System에 대한 학습 과정 중 리눅스 운영체제를 경험하기 위해
윈도우나 맥OS 위에서 새로운 운영체제를 실행하기 위해
가상의 컴퓨터를 두는 소프트웨어 프로그램으로 알고 있다.

큰 관점에서 보았을 때

  • 리눅스 학습을 위한 가상머신
  • JVM

두 가상머신은 모두 실제 하드웨어 환경 위에서 추상적인 실행환경을 제공한다는 점에서
동일한 개념 계열을 의미하는 것이다.

단지 해당 가상머신이 어떤 것을 가상화하느냐에 차이가 있을 뿐이다.

리눅스 학습을 위한 가상 머신의 경우
: CPU, 메모리(DRAM, 보조기억장치 etc.), 네트워크 등 컴퓨터 자체를 가상화

Java Virtual Machine
: Java 프로그램이 실행될 수 있는 실행 환경을 가상화

정확히하면 JVM은 .class라는 바이트코드를 읽어서 실행하는 엔진인 것이다.

바이트코드란?
특정 하드웨어가 아닌 가상 환경에서 돌아가는 실행 프로그램을 위한 이진 표현법

JVM은, javac가 우리가 작성한 .java 소스코드를
.class 바이트코드로 변환한 것을 실행하여 주는 것이다.

이런 중간 과정을 굳이 거쳐서 자바 프로그램을 실행하는 것일까?

C/C++과 같은 네이티브 코드는 CPU와 운영체제에 맞추어 컴파일되는데

이는 동일한 소스코드라 하여도,
각각의 OS나 CPU아키텍처에 따라 정상 실행 여부가 달라질 수 있다.

그러나 자바 프로그램의 경우
동일 소스코드를 서로 다른 OS, CPU아키텍처를 가진 시스템에서 실행하여도
JVM이라는 실행 환경 위에서 .class 바이트코드를 실행하기에

자바언어가 운영체제에 상관없이 독립성을 가질 수 있는 것이다.


JVM: Java Virtual Machine 학습의 필요성

개발자를 지망하는 우리는 왜 JVM을 학습해야할까?
언어의 문법이나, 구현하고자 하는 서비스의 로직, 프레임워크가 제공하는 기능과 사용법 등
집중해야될 것들은 이미 많은데 왜 자동으로 코드를 실행해주는 JVM에 대하여 알아야할까?

결론부터 말하자면 JVM에 대한 깊은 이해는 개발자로서 문제 해결 능력을 키울 수 있다.

물론 JVM의 동작원리까지 들어가는 깊은 레벨의 문제는
실제 개발 환경에서는 자주 발생하지 않는다.

그럼에도 좋은 개발자, 즉 경쟁력 있는 개발자가 되기 위해서는
이러한 깊은 레벨의 문제도 인지하고, 해결할 수 있는 능력을 키워야 한다.

JVM의 근본 원리를 이해한다는 것은 우리가 작성한 코드가
어떤 과정을 거쳐 내부에서 실행되는 지 자세히 알아가기 위한 기본 체력이 되며
결과적으론 메모리 누수성능 저하같은 까다로운 문제를 깊은 영역에서 캐치하고 해결할 수 있는 것이다.
참고 예시) JVM 웜업 // 이에 대해 추가 포스팅 예정


JVM 세부 동작

위 그림은 JVM 내에서 자바 프로그램이 어떤 방식으로 진행되며 실행되는 지
전반에 대한 내용을 아키텍처 다이어그램으로 표현한 것이다.

이 중 .class 바이트 코드로의 변환을 java compiler인 javac가 도와주며,
이를 JVM이 읽고 실행한다는 것을 앞선 내용에서 설명하였다.

이후 JVM 내부에서 일어나는 각 과정과 동작에 대하여 알아보도록 하겠다.

1. Memory Area

JVM은 프로그램 실행 중 필요한 데이터가 담기는 메모리 영역을 나누어 관리한다.
이러한 자바 어플리케이션이 실행되는 도중 사용되는 데이터들이 저장되는 메모리 공간을

런타임 데이터 영역 (Runtime Data Area)이라 하며,
하단 그림의 Memory Area Of JVM에 해당한다.

크게 5개의 메모리 영역으로 나뉘며,

  • Method Area Heap Area의 경우
    자바 어플리케이션에서 실행되는 각 스레드가 서로 공유
  • Stack Area, PC Register, Native Method Stack
    각각의 스레드가 모두 개별적으로 존재한다.

이러한 개념은 동시성을 이해하는데 있어 주요한 기초가 되기에 알아두어야 한다.

각 런타임 데이터 영역에 대해 알아보면

  • Method Area(스레드 공유 공간)
    Class Loader가 읽어온 Class의 정보(클래스 메타데이터)가 저장되며
    (JVM(Java Virtual Machine) (2)에서 자세히)
    static 변수와 메서드 바이트코드, 상수 풀이 이곳에 저장된다.

    Method Area는 공유 영역이기에,
    하나의 스레드가 static 변수를 변경하면 다른 모든 스레드에 영향을 줄 수 있다.

    따라서 공유 상태의 static 변수를 변경할 경우
    race condition을 유발할 수 있기에 신중하게 선언해야한다.

    private static final int STATIC_DATA = 10;

    런타임 상수 풀(Runtime Constant Pool)이란
    .class 바이트코드안에는
    문자열 리터럴, 숫자 상수, 클래스 이름,
    참조(symbolic reference)같은 정보가 constant pool 형태로 존재하는데,

    JVM에서 클래스(.class파일, 바이트코드)를 로딩하여 이러한 정보들을
    런타임에서 사용 가능한 상수 풀에 재구성한 것이다.

    System.out.println();

    위와 같은 메서드 참조도 결국 상수 풀에 등록되어 있는 참조 정보를 통해 연결된다.

    참조의 경우 초기 .class 바이트코드를 클래스 로딩한 이후에는
    Runtime Constant Pool 내부에
    symbolic reference 상태로 존재하지만,

    실제로 해당 참조가 사용되는 시점에 클래스로더의 Resolution 과정에서
    실제 주소를 가리키는 direct reference로 변환되어 존재하게 된다.

  • Heap Area(스레드 공유 공간)
    객체와 배열이 저장되는 공간으로, new 키워드로 생성한 실제 객체가 담기는 곳이다.
    즉, 클래스의 인스턴스와 배열을 위한 메모리 공간이 할당되는 런타임 데이터 영역이다.

    해당 메모리 영역은 GC(Garbage Collector)가 관리하는 공간으로,
    new키워드를 통해 생성된 객체가 더 이상 참조되지 않으면
    GC에 의해 자동으로 해당 객체의 메모리 공간이 회수된다.

    GC에 대한 추가 내용은 JVM(Java Virtual Machine) (2)에서 다루도록 한다.

  • Stack Area(스레드 간 독립적 공간)
    쉽게 이해하면 메서드들의 작업 공간이라 할 수 있다.

    스레드마다 독립적으로 생성되며,
    스택에 저장되는 데이터는 다른 스레드가 침범할 수 없다. thread safety
    추가로 각 스레드별 스택 공간에 메서드가 호출될 때마다
    메서드별 스택 프레임(Stack Frame)이 쌓이게 되고 메서드가 종료되면 스택마다 LIFO구조에 따라서 스택프레임이 제거된다.
  • PC Register(스레드 간 독립적 공간)
    JVM에서 동작하는 각 스레드간 실행 중인 명령어의 주소를 저장하는 공간으로,
    스레드의 현재 실행 중인 JVM 바이트코드 명령어 메모리 주소가 담긴다.

    Program Counter Register와 동작이 비슷하다. 실제로 관련이 있나?

    실제로 개념적으론 동일하다, JVM은 가상 실행 환경이기 때문에,
    프로세스를 실행할 때 필요한 CPU의 내부 레지스터(Program Counter)를
    JVM이 소프트웨어적으로 구현했다고 할 수 있다.

    이때 JVM의 PC Register 영역은 이름만 레지스터일 뿐
    실제로 레지스터를 사용하지는 않는다.

  • Naitive Method Stack(스레드 간 독립적 공간)
    해당 영역은 자바 코드가 아닌 C/C++, 어셈블리
    타 언어로 작성된 네이티브 코드를 실행하기 위해 사용되는 별도의 스택 메모리 영역이다.

    자바 어플리케이션 내에서 외부 네이티브 함수를 호출한다면,
    해당 메서드는 스택 영역이 아닌 본 Naitive Method Stack에 쌓여 실행되는 것이다.

    해당 영역에 저장된 네이티브 코드를 실행할 때는
    PC Register 영역에 있는 스레드 별 실행 명령어의 주소가 undefined가 된다.

    JNI(Java Native Interface)란?
    자바에서 운영체제나 하드웨어와 밀접한 기능을 사용해야 할 때는 네이티브 코드가 필요하다.
    이때 JNI 인터페이스를 통해 외부 네이티브 메서드를 호출할 수 있다.


profile
개발 및 IT기술에 대해 정리하고 기록합니다.

0개의 댓글