본문 바로가기
개발

[java] JVM 개념 및 기능 : 왜 쓰는 걸까?

by 주주병 2024. 10. 10.
728x90
반응형

JVM 핵심정리

면접 질문에서 JVM은 무엇인가요? 라는 질문을 받았는데 제대로 답하지 못했습니다.

 

JVM은 왜 쓰는 걸까요?

자바는 한 번만 프로그램을 작성하면 다양한 컴퓨터 환경(Windows, Mac, Linux)에서 실행할 수 있다는 강력한 특징이 있습니다.

이걸 가능하게 해주는 것이 바로 JVM(Java Virtual Machine)이라는 가상 머신입니다.

 

JVM이란 무엇인가?

JVM은 Java 프로그램을 실행할 수 있는 일종의 가상 컴퓨터입니다.

보통 컴퓨터에서 프로그램이 실행되려면 운영 체제(Windows, Mac 등)에 맞게 기계어로 변환되어야 합니다.

 

하지만 Java는 바이트코드(Bytecode)라는 중간 형태로 변환되고, 이 바이트코드를 JVM이 해석하여 어떤 운영 체제에서도 실행할 수 있게 도와줍니다.

 

바이트코드란 무엇일까?

바이트코드사람이 작성한 Java 코드를 컴퓨터가 이해할 수 있는 중간 언어라고 생각하면 돼요.

우리가 Java로 코드를 작성하면, 그 코드는 .java 파일로 저장됩니다.

 

이 코드를 컴파일하게 되면 바이트코드로 변환되는데, 이 바이트코드는 컴퓨터가 바로 이해할 수는 없지만, JVM이 이걸 해석해줍니다.

  • 쉽게 말하면: 자바 코드는 바로 기계어로 번역되지 않고, 중간 단계인 바이트코드로 번역된 후, JVM이 이 바이트코드를 운영 체제에 맞게 변환해서 실행하는 것이죠. 이 덕분에 같은 자바 프로그램이 어떤 컴퓨터에서도 실행될 수 있는 겁니다.

 

왜 JVM을 써야 할까?

우리가 JVM을 사용하는 이유는 자바 프로그램이 어느 컴퓨터에서나 실행되도록 하기 위함입니다.

 

자바 프로그램은 한 번 작성하면 여러 운영 체제에서 실행이 가능하죠. 그 이유는 JVM이 각 운영 체제에 맞게 자바 프로그램을 해석해주기 때문입니다.

 

예를 들어, Windows에서는 Windows용 JVM이, Mac에서는 Mac용 JVM이 바이트코드를 운영 체제에서 실행 가능한 코드로 변환해줍니다.

  • 다시 말해서: 자바는 한 번 작성해서 여러 곳에서 실행할 수 있습니다. 자바 코드를 작성하면 JVM이 바이트코드를 각 운영 체제에 맞게 변환해주니까, 따로 코드를 수정하지 않아도 어디서나 잘 실행됩니다.

 

JVM의 주요 기능

JVM은 자바 프로그램을 단순히 실행하는 것 외에도, 여러 가지 중요한 기능을 담당합니다. 이 기능들은 자바 프로그램을 더 편리하고 안전하게 실행할 수 있게 도와줘요.

jvm 구성

 

클래스 로더 (Class Loader)

클래스 로더는 JVM의 첫 번째 단계로, 자바 프로그램의 클래스 파일(.class)메모리로 로드하는 역할을 합니다.

자바에서 코드를 작성하면, 컴파일러가 해당 코드를 바이트코드로 변환하여 .class 파일을 생성합니다.

 

클래스 로더는 이 파일을 읽어 JVM이 실행할 수 있도록 준비하는 과정을 담당합니다.

 

기능

  • 로드(Loading): .class 파일을 파일 시스템 또는 네트워크에서 가져와 메모리로 로드합니다. 자바의 클래스 파일은 이 과정을 통해 메모리로 올라갑니다.
  • 연결(Linking):
    1. 검증(Verification): 클래스 파일이 올바르게 형성되어 있는지 검증합니다. 자바의 바이트코드가 JVM이 이해할 수 있는 형식인지, 불법적인 코드가 포함되어 있지 않은지 확인합니다.
    2. 준비(Preparation): 클래스에서 사용될 변수나 메모리 공간을 할당합니다. 이 단계에서는 아직 값을 초기화하지 않고, 필요한 데이터 구조를 준비합니다.
    3. 해결(Resolution): 클래스에서 참조하는 외부 클래스, 메서드, 필드 등을 동적으로 연결하여 실제 메모리 주소를 할당합니다. 예를 들어, System.out.println을 호출할 때 System 클래스와 그 메서드들이 메모리에 연결됩니다.
  • 초기화(Initialization): 클래스 로더가 메모리로 올린 클래스의 정적 필드를 초기화하고, 클래스 초기화 블록을 실행합니다. 클래스 로더는 클래스가 처음 참조될 때 이 작업을 수행합니다.

클래스 로더의 유형

  • 부트스트랩 클래스 로더(Bootstrap ClassLoader): 가장 기본적인 클래스 로더로, JVM이 제공하는 핵심 라이브러리(예: java.lang.*)를 로드합니다.
  • 확장 클래스 로더(Extension ClassLoader): 부트스트랩 클래스 로더에 의해 로드되지 않은 확장 라이브러리들을 로드합니다. 자바 설치 경로의 ext 디렉토리에 있는 라이브러리들이 이 클래스로 로드됩니다.
  • 애플리케이션 클래스 로더(Application ClassLoader): 애플리케이션의 사용자 정의 클래스들을 로드합니다. 우리가 작성한 자바 코드나 프로젝트에 포함된 .class 파일들은 이 로더에 의해 메모리에 올라갑니다.

 

실행 엔진 (Execution Engine)

실행 엔진JVM이 로드한 바이트코드를 실제로 실행하는 부분입니다. 자바 프로그램의 바이트코드는 바로 실행될 수 없기 때문에, 실행 엔진이 이를 기계어로 변환하여 CPU가 이해하고 처리할 수 있게 합니다.

 

기능

  • 바이트코드 해석: 실행 엔진은 클래스 로더가 로드한 바이트코드를 해석하여 기계어로 변환합니다. 여기에는 두 가지 방식이 있는데, 인터프리터(Interpreter)JIT(Just-In-Time) 컴파일러가 함께 작동하여 바이트코드를 처리합니다.
  • 스택 기반 명령어 처리: 자바 바이트코드는 스택 기반 언어이기 때문에, 실행 엔진은 메서드 호출이나 수식을 계산할 때 스택을 사용합니다. 즉, 명령어를 처리할 때 스택을 통해 연산하고, 결과를 다시 스택에 쌓습니다.
  • 실행 환경 제공: 메서드 호출, 메서드 반환, 예외 처리 등을 관리하면서 프로그램이 올바르게 실행될 수 있는 환경을 제공합니다.
💡스택 기반 언어란?
int a = 5;
int b = 3;
int c = a + b;​

 

이러한 자바 코드가 있을 때 스택 기반 언어는

숫자 5를 스택에 Push: a 값을 스택에 넣습니다.
-> 스택 상태: [5]
숫자 3을 스택에 Push: b 값을 스택에 넣습니다.
-> 스택 상태: [5, 3]
덧셈 연산 수행: 스택에서 두 값을 꺼내(Pop) 더한 후, 그 결과를 다시 스택에 넣습니다.
-> 스택 상태: [8]
결과 저장: 스택에서 결과를 꺼내(Pop) c 변수에 저장합니다.

이런 식으로 스택을 이용해서 연산을 하는 언어를 스택 기반 언어라고 합니다.

그렇다면 반대는 무엇일까요?

바로 레지스터 기반 언어입니다.

레지스터 기반 언어는 스택이 아니라 CPU의 레지스터(작고 빠른 메모리)를 사용하여 연산을 수행합니다.
스택 기반 언어는 스택을 사용하기 때문에 코드가 더 간단하지만, 레지스터 기반 시스템에 비해 약간 느릴 수 있습니다.
그럼에도 불구하고, 자바가 스택 기반 시스템을 사용하는 이유는 무엇일까요?

자바가 스택 기반으로 설계된 이유는 이식성 때문입니다.

레지스터 기반 시스템은 CPU 아키텍처에 따라 레지스터 개수나 사용 방식이 달라지기 때문에, 특정 하드웨어에 의존하는 반면,

스택 기반 언어는 어느 플랫폼에서도 일관된 방식으로 실행될 수 있습니다.

자바는 "한 번 작성하면 어디서나 실행된다"는 철학을 따르기 때문에, 스택 기반으로 JVM을 설계하여 다양한 운영 체제와 하드웨어에서 동일하게 동작할 수 있도록 했습니다.

 

인터프리터 (Interpreter)

인터프리터는 자바 바이트코드를 한 줄씩 순차적으로 해석하여 실행하는 역할을 합니다. 이 방식은 즉시 실행할 수 있는 장점이 있지만, 자주 실행되는 코드를 매번 해석해야 하기 때문에 성능이 떨어질 수 있습니다.

 

기능

  • 바이트코드를 즉시 해석: 자바 프로그램이 실행되면, 바이트코드를 한 줄씩 해석하고 그에 맞는 기계어로 변환합니다. 프로그램이 처음 실행될 때 빠르게 시작할 수 있지만, 동일한 코드가 반복될 때마다 해석하는 과정이 필요해 속도가 느려질 수 있습니다.
  • 빠른 시작: 인터프리터는 초기 실행 속도가 빠르기 때문에, 프로그램이 바로 실행되기 시작할 수 있습니다. 그러나 장기적으로는 성능 최적화가 필요할 때가 있습니다.

 

JIT 컴파일러 (Just-In-Time Compiler)

JIT 컴파일러는 자바 프로그램을 실행하면서 자주 실행되는 부분(핫스팟 코드)을 찾아내고, 해당 부분을 기계어로 미리 컴파일하여 성능을 최적화하는 역할을 합니다.

 

인터프리터가 반복적으로 해석해야 하는 코드를 JIT 컴파일러가 미리 컴파일하여, 이후에는 더 빠르게 실행할 수 있도록 합니다.

 

기능

  • 자주 사용되는 코드 최적화: JIT 컴파일러는 실행 중 반복적으로 호출되는 메서드루프 같은 코드를 찾아내어, 이를 기계어로 미리 컴파일합니다. 이렇게 미리 컴파일된 코드는 다시 해석하지 않고 빠르게 실행됩니다.
  • 런타임 성능 향상: 자주 사용되는 코드를 컴파일하여 성능을 크게 향상시킵니다. JIT 컴파일러는 실행 중인 프로그램을 분석하고 최적화하기 때문에, 프로그램이 오래 실행될수록 성능이 좋아질 수 있습니다.
  • 최적화 기법 사용: JIT 컴파일러는 다양한 최적화 기법을 사용하여 더 빠르게 실행될 수 있도록 합니다. 예를 들어, 인라인(inlining), 루프 최적화(loop unrolling) 등의 기법을 적용합니다.
💡 인라인 vs 루프 최적화

인라인이란?
인라이닝은 함수나 메서드를 호출하는 대신, 그 내용을 호출한 곳에 직접 삽입하는 최적화 기법입니다.

메서드 호출에는 추가적인 오버헤드(함수 호출과 관련된 메모리 작업, 스택에 매개변수 전달, 반환 등)가 발생하기 때문에, 인라이닝을 통해 함수 호출 오버헤드를 제거하고 성능을 향상시킬 수 있습니다.

기존의 함수 호출 방식:

int add(int a, int b) {
    return a + b;
}

int result = add(5, 3);

인라인 방식:
int result = 5 + 3;

 

그니깐 함수를 호출해서 괜한 메모리 작업을 하느니 아싸리 통채로 가져와서 박아버리는 느낌입니다.


루프 최적화
루프 최적화는 컴파일러가 루프 내에서 반복적으로 수행되는 불필요한 연산을 줄이거나, 루프의 구조를 바꾸어 성능을 높이는 기법입니다. 루프는 자주 사용되며 실행 시간이 많이 걸리기 때문에, 최적화를 통해 큰 성능 향상을 기대할 수 있습니다.

일반적인 코드:
for (int i = 0; i < 5; i++) {
    process(i);
}


개선된 코드:

process(0);
process(1);
process(2);
process(3);
process(4);

이런 식으로 바꿔서 인덱스 증가, 조건 검사와 같은 오버헤드를 줄일 수 있다

가비지 콜렉터 (Garbage Collector)

가비지 콜렉터는 JVM의 자동 메모리 관리 기능으로, 프로그램이 더 이상 사용하지 않는 객체들을 찾아내어 메모리에서 제거하는 역할을 합니다. 자바는 개발자가 직접 메모리를 관리하지 않아도, JVM이 자동으로 메모리를 관리해줍니다.

 

기능

  • 메모리 해제: 가비지 콜렉터는 더 이상 참조되지 않는 객체를 메모리에서 제거하여, 메모리 누수를 방지합니다. 이를 통해 메모리가 효율적으로 사용될 수 있습니다.
  • 힙 메모리 관리: JVM의 힙 영역에 저장된 객체들 중 더 이상 참조되지 않는 객체를 탐색하여 제거합니다. 프로그램 실행 중 동적으로 생성된 객체들은 힙 영역에 저장되며, 가비지 컬렉터가 이 영역을 주기적으로 청소합니다.
  • 마이너 GC와 메이저 GC: 가비지 컬렉션은 크게 **마이너 GC(Minor GC)**와 **메이저 GC(Major GC)**로 나뉩니다. 마이너 GC는 Young Generation에서 주기적으로 발생하며, 메이저 GC는 Old Generation에서 실행됩니다.

가비지 콜렉터에 대해서 자세히 알고 싶으시다면 아래의 글을 봐주세요.

 

[JAVA] 자바의 가비지컬렉션 개념과 동작원리

가비지 컬렉션(Garbage Collection)의 정의와 동작 원리가비지 컬렉션(Garbage Collection, GC)은 메모리 관리 기법으로, 더 이상 필요하지 않거나 참조되지 않는 객체를 자동으로 메모리에서 제거하는 프로

gotobill.tistory.com

 

728x90

런타임 데이터 영역 (Runtime Data Area)

런타임 데이터 영역은 자바 프로그램이 실행되면서 사용하는 메모리 공간을 말합니다.

 

JVM은 프로그램 실행 중 여러 데이터 영역을 사용하여 메모리 관리를 합니다.

 

여기에는 Heap(힙), Stack(스택), PC 레지스터, 메서드 영역, 네이티브 메서드 스택 등이 포함됩니다.

 

기능

  • Heap(힙): 모든 객체가 저장되는 메모리 공간입니다. 객체는 동적으로 할당되며, 가비지 컬렉터가 이 힙 영역을 관리합니다.
  • Stack(스택): 메서드가 호출될 때 사용되는 지역 변수들이 저장됩니다. 각 스레드마다 독립적인 스택을 가지고 있으며, 메서드 호출과 관련된 데이터를 저장하고 삭제합니다.
  • PC 레지스터: 프로그램 카운터로, 현재 실행 중인 JVM 명령어의 주소를 저장하는 공간입니다. 각 스레드마다 하나씩 할당됩니다.
  • 메서드 영역: 클래스와 메서드 정보가 저장되는 메모리 영역입니다. 프로그램이 실행되기 전, 클래스 로더가 로드한 클래스와 인터페이스, 메서드, 필드 정보들이 이곳에 저장됩니다.
  • 네이티브 메서드 스택: 자바 외의 네이티브 코드(C, C++)로 작성된 메서드를 실행할 때 사용되는 스택입니다. 네이티브 메서드는 주로 성능 향상이나 시스템 호출에 사용됩니다.

런타임 데이터 영역

 

JVM이 중요한 이유

  • 플랫폼 독립성: 한 번 작성한 프로그램이 어느 컴퓨터에서도 실행될 수 있도록 해줍니다.
  • 메모리 관리: 개발자가 직접 메모리 관리를 하지 않아도, JVM이 자동으로 불필요한 메모리를 정리해줍니다.
  • 보안: JVM은 바이트코드를 실행하기 전에 검증을 거쳐, 악성 코드로부터 프로그램을 보호합니다.
  • 성능 최적화: JIT 컴파일러가 자주 실행되는 코드를 미리 기계어로 변환하여 성능을 높입니다.

 

JVM의 동작 과정 요약

  1. Java 코드 작성: 자바로 프로그램을 작성하고, .java 파일로 저장합니다.
  2. 컴파일: 이 자바 파일을 컴파일하면 **바이트코드(.class 파일)**로 변환됩니다.
  3. JVM 실행: JVM이 이 바이트코드를 읽고, 각 운영 체제에 맞게 기계어로 변환하여 프로그램을 실행합니다.
  4. 가비지 컬렉션: 프로그램이 실행되는 동안 JVM이 더 이상 필요 없는 메모리를 자동으로 정리해줍니다.

 

JDK vs JRE

JDK (Java Development Kit)

JDK는 자바 애플리케이션을 개발하고 컴파일하는 데 필요한 도구들의 모음입니다. 개발자가 자바 애플리케이션을 작성할 때 사용하는 키트입니다.

 

JDK의 주요 구성 요소

  • JRE (Java Runtime Environment): JDK 내부에는 JRE가 포함되어 있습니다. 즉, JDK는 자바 프로그램을 개발하는 데 필요한 도구와 함께 자바 프로그램을 실행하는 데 필요한 환경(JRE)을 모두 포함하고 있습니다.
  • 컴파일러 (javac): 자바 소스 코드를 바이트코드(.class 파일)로 변환하는 컴파일러입니다. 개발자가 작성한 자바 코드를 컴파일하여 실행 가능한 형태로 변환합니다.
  • 디버거 (jdb): 자바 프로그램을 실행하며 디버깅할 수 있는 도구입니다.
  • 자바 도큐먼트 생성기 (javadoc): 자바 코드에서 문서화 주석을 기반으로 API 문서를 생성할 수 있는 도구입니다.
  • 기타 개발 도구: 자바 프로그램을 빌드, 패키징, 테스트하는 데 필요한 도구들이 포함되어 있습니다.

JDK를 사용해야 하는 경우

  • 자바 프로그램을 작성, 컴파일, 디버깅하거나 빌드할 때 JDK가 필요합니다. 즉, 자바 애플리케이션을 개발하는 개발자라면 JDK를 설치해야 합니다.

JRE (Java Runtime Environment)

JRE는 자바 애플리케이션을 실행하기 위한 환경입니다. 개발자가 아닌 사용자가 자바 프로그램을 실행할 때 필요한 요소들만 포함되어 있습니다.

JRE의 주요 구성 요소

  • JVM (Java Virtual Machine): 자바 프로그램을 실행하는 가상 머신입니다. 자바 프로그램이 동작할 수 있도록 바이트코드를 기계어로 변환해 실제 컴퓨터에서 프로그램을 실행하게 해줍니다.
  • 라이브러리: 자바 프로그램이 실행될 때 필요한 표준 라이브러리(클래스 파일들)가 포함되어 있습니다. 예를 들어, 자바에서 자주 사용하는 java.lang이나 java.util 같은 라이브러리들이 이곳에 포함됩니다.
  • 기타 런타임 지원 파일: 자바 프로그램을 실행하는 데 필요한 파일들이 포함됩니다.

JRE를 사용해야 하는 경우

  • 자바 프로그램을 실행하기만 한다면 JRE만 있으면 충분합니다. 예를 들어, 웹 애플리케이션에서 자바 애플리케이션을 구동하는 경우, JRE가 필요합니다. 개발자가 아니라면 JDK 대신 JRE만 설치해도 됩니다.
728x90
반응형