About Java

koonlx·2024년 11월 4일

Java

목록 보기
1/2

JVM 구조


이미지 출처: jvm-java-virtual-machine

클래스 로더 (Classloader)

클래스 로더는 JVM의 서브시스템으로서 클래스 파일을 로드하는 데 사용된다. 자바 프로그램을 실행할 때마다 가장 먼저 클래스 로더에 의해 프로그램이 로드된다.

Java 9 이전

  • 부트스트랩 클래스 로더 (Bootstrap ClassLoader): 이는 첫 번째 클래스 로더로서, 익스텐션 클래스 로더의 상위 클래스입니다. java.lang, java.net, java.util, java.io, java.sql 등의 자바 표준 에디션 클래스 파일을 포함하는 rt.jar 파일을 로드한다.
  • 익스텐션 클래스 로더 (Extension ClassLoader): 부트스트랩 클래스 로더의 자식이자 시스템 클래스 로더의 부모 클래스 로더이다. $JAVA_HOME/jre/lib/ext 디렉토리에 위치한 JAR 파일들을 로드한다.
  • 시스템/애플리케이션 클래스 로더 (System/Application ClassLoader): 익스텐션 클래스 로더의 자식 클래스 로더이다. 클래스패스에서 클래스 파일들을 로드한다. 기본적으로 클래스패스는 현재 디렉토리로 설정되어 있다. -cp 또는 -classpath 스위치를 사용하여 클래스패스를 변경할 수 있다. 애플리케이션 클래스 로더라고도 알려져 있다.

Java 9 이후

  • 부트스트랩 클래스 로더 (Bootstrap ClassLoader): java.base 모듈만 로드하며 java.base에는 java.lang, java.util 등의 핵심 클래스가 포함된다.
  • 플랫폼 클래스 로더 (Platform ClassLoader): Java 9에서 새롭게 도입된 클래스 로더이며 java.sql, java.xml, java.logging 등 표준 플랫폼 모듈을 로드한다. ClassLoader.getPlatformClassLoader()를 통해 접근할 수 있다.
  • 애플리케이션 클래스 로더 (Application ClassLoader): 애플리케이션 모듈 및 클래스패스에 있는 클래스를 로드한다. ClassLoader.getSystemClassLoader()를 통해 접근할 수 있다.

info: Java 9부터는 모듈 시스템의 도입으로 익스텐션 클래스 로더가 더 이상 사용되지 않습니다.

// 클래스 로더의 이름을 출력하는 예제
public class ClassLoaderExample {
    public static void main(String[] args) {
        // 현재 클래스의 클래스 로더 이름을 출력합니다.
        // 이 클래스는 애플리케이션/시스템 클래스 로더에 의해 로드됩니다.
        Class<?> c = ClassLoaderExample.class;
        System.out.println(c.getClassLoader());

        // String 클래스의 클래스 로더 이름을 출력합니다.
        // String은 rt.jar에 있는 내장 클래스이므로 부트스트랩 클래스 로더에 의해 로드되며, null을 반환합니다.
        System.out.println(String.class.getClassLoader());
    }
}
# output
jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487
null


이미지 출처: memory-management-in-java

JVM 메모리 구조

Java 프로그램은 실행 시 Java Virtual Machine (JVM) 위에서 동작하며, JVM은 메모리를 다음과 같이 구분된다.

  • 메서드 영역(Method Area):
    클래스 로더에 의해 로드된 클래스 메타데이터(필드, 메서드, 인터페이스 등)가 저장된다.
    런타임 상수 풀도 이 영역에 포함되며, 상수 및 리터럴을 저장한다.
    모든 스레드가 공유하는 영역이다.
  • 힙 영역(Heap Area):
    객체와 배열이 동적으로 할당되는 영역이다.
    가비지 컬렉션의 대상이 된다.
    모든 스레드가 공유하는 영역이다.
  • 스택 영역(Stack Area):
    각 스레드마다 생성되며, 메서드 호출 시마다 스택 프레임이 추가된다.
    지역 변수, 매개변수, 연산 중간 결과 등이 저장된다.
  • PC 레지스터(Program Counter Register):
    각 스레드가 실행할 JVM 명령의 주소를 저장한다.
  • 네이티브 메서드 스택(Native Method Stack):
    Java 외의 네이티브 코드를 실행하기 위한 스택입니다.

기본 자료형(Primitive Types)과 메모리 할당

Java의 기본 자료형은 스택 영역에 직접 저장되며, 총 8가지이다.

자료형크기범위비트 패턴
byte8비트-128 ~ 1272의 보수
short16비트-32,768 ~ 32,7672의 보수
int32비트-231 ~ 231 - 12의 보수
long64비트-263 ~ 263 - 12의 보수
float32비트±1.4E-45 ~ ±3.4028235E38IEEE 754
double64비트±4.9E-324 ~ ±1.7976931348623157E308IEEE 754
char16비트'\u0000' (0) ~ '\uffff' (65,535)유니코드
booleanJVM에 따라 다름true 또는 false1비트 또는 1바이트

메모리 저장 방식 상세 분석

  • 정수형 (byte, short, int, long)
    • 2의 보수법으로 부호를 표현한다.
    • 오버플로 및 언더플로 발생 시 값이 순환한다.
  • 실수형 (float, double)
    • IEEE 754 부동소수점 표준을 따른다.
    • float: 1비트 부호, 8비트 지수, 23비트 가수.
    • double: 1비트 부호, 11비트 지수, 52비트 가수.
  • 문자형 (char)
    • UTF-16 인코딩을 사용하여 유니코드 문자 표현.
    • 서플리먼트리 문자(2바이트를 초과하는 문자)는 두 개의 char로 표현된다.
  • 불리언형 (boolean)
    • JVM 구현에 따라 1비트 또는 1바이트로 저장된다.
    • 배열에서는 1바이트를 사용하여 주소 계산을 용이하게 한다.

참조 자료형(Reference Types)과 메모리 할당

참조 자료형은 힙 영역에 객체를 생성하고, 스택 영역에는 그 객체의 참조값(주소) 을 저장한다.

  • 클래스 타입
    • 사용자 정의 객체로, 필드와 메서드를 포함합니다.
    • 예: MyClass obj = new MyClass();
  • 배열 타입
    • 동일한 타입의 변수들을 연속적으로 저장.
    • 배열 자체는 객체로 취급되며, 힙에 저장됩니다.
  • 인터페이스 및 열거형
    • 구현 클래스나 열거형 인스턴스가 힙에 저장됩니다.

객체의 메모리 구조

  • 객체 헤더
    • Mark Word: 객체의 동기화 정보, 해시 코드 등을 저장.
    • 클래스 포인터: 해당 객체의 클래스 메타데이터를 가리킴.
  • 인스턴스 변수
    • 클래스에 정의된 필드들이 저장됩니다.
  • 패딩(Padding)
    • 메모리 정렬을 위해 사용됩니다.

스택 프레임(Stack Frame) 상세 구조

각 메서드 호출 시 생성되는 스택 프레임은 다음과 같은 구조를 가진다.

  • 로컬 변수 배열(Local Variable Array)
    • 메서드의 매개변수와 지역 변수가 저장된다.
    • 인덱스로 접근하며, int는 1 슬롯, long과 double은 2 슬롯을 사용한다.
  • 오퍼랜드 스택(Operand Stack)
    • 연산의 중간 결과를 저장하는 스택 구조.
    • 바이트코드 명령어에 의해 PUSH와 POP이 이루어진다.
  • 프레임 데이터(Frame Data)
    • 동적 링크(Dynamic Link): 호출한 메서드의 스택 프레임을 가리키는 포인터.
    • 메서드 반환 주소: 메서드 종료 시 복귀할 주소를 저장.

가비지 컬렉션(Garbage Collection) 메커니즘

객체의 생존 기간과 세대 구분

  • Young Generation
    • 새롭게 생성된 객체가 할당된다.
    • Eden Space와 두 개의 Survivor Space (S0, S1) 로 구성.
    • Minor GC가 주로 수행된다.
  • Old Generation
    • Young Generation에서 생존한 장수 객체가 이동된다.
    • Major GC 또는 Full GC가 수행된다.

가비지 컬렉션 알고리즘

  • Mark-and-Sweep
    • 마크 단계: 도달 가능한 객체를 식별하고 마크한다.
    • 스위프 단계: 마크되지 않은 객체를 회수한다.
  • Mark-and-Compact
    • 컴팩션 단계를 추가하여 메모리 단편화를 줄인다.
  • Copying
    • Young Generation에서 사용되며, 살아있는 객체를 다른 공간으로 복사하고 기존 공간을 정리한다.

루트 집합(Root Set)

  • GC Roots: 도달 가능한 객체의 시작점으로, 다음을 포함한다:
    • 스택 프레임의 로컬 변수
    • 정적 필드
    • JNI 참조

메모리 최적화 기법

객체 풀링(Object Pooling)

  • 빈번한 객체 생성과 소멸을 줄이기 위해 재사용 가능한 객체 풀을 사용한다.
  • 예: Integer.valueOf(int i)는 -128에서 127 사이의 값을 캐시한다.

불변 객체(Immutable Object) 사용

  • 불변 객체는 상태 변경이 불가능하므로, 스레드 안전성과 캐싱 효율성이 높아진다.
  • 예: String, Integer (자동 박싱된 값).

메모리 누수 방지

  • 컬렉션에서 사용하지 않는 객체를 제거한다.
  • 이벤트 리스너나 콜백에서의 약한 참조(Weak Reference) 사용을 고려한다.

상수 풀(Constant Pool)과 문자열 관리

런타임 상수 풀(Runtime Constant Pool)

  • 클래스 파일에 있는 상수 풀을 로드하여 메서드 영역에 저장한다.
  • 동적 상수는 String.intern() 메서드를 통해 상수 풀에 추가할 수 있다.

문자열 상수 풀(String Constant Pool)

  • 동일한 문자열 리터럴은 상수 풀에서 공유된다.
  • new String("text")는 힙에 새로운 객체를 생성하므로 주의가 필요하다.

메모리 모델과 스레드 안전성

Java 메모리 모델(Java Memory Model, JMM)

  • 변수의 가시성(Visibility) 과 재배열(Reordering) 을 규정한다.
  • volatile 키워드: 변수의 변경이 즉시 다른 스레드에 가시적으로 반영되도록 한다.
  • happens-before 관계: 메모리 간섭을 방지하기 위한 실행 순서 보장.

동기화(Synchronization)

  • 모니터 락(Monitor Lock) 을 통해 객체에 대한 동기화를 수행한다.
  • synchronized 키워드: 메서드나 블록에 적용하여 원자성을 보장한다.

전문적인 메모리 디버깅과 튜닝

메모리 프로파일링 도구

  • JVisualVM, Eclipse Memory Analyzer 등을 사용하여 힙 덤프 분석.
  • JConsole, JProfiler로 실시간 메모리 사용량 모니터링.

GC 로그 분석

  • JVM 옵션 -Xlog:gc*를 사용하여 가비지 컬렉션 로그를 수집.
  • GC 동작을 분석하여 메모리 누수나 성능 병목 현상을 파악.

JVM 옵션 조정

  • 힙 크기 조절: -Xms(초기 힙 크기), -Xmx(최대 힙 크기).
  • GC 알고리즘 선택: -XX:+UseG1GC, -XX:+UseConcMarkSweepGC 등.
profile
Server Developer

0개의 댓글