JVM의 핵심 원리 이해: 코드 중심의 설명

Java의 플랫폼 독립성은 JVM의 강력한 설계에서 비롯됩니다. 이 문서에서는 클래스 로딩, 메모리 관리, GC 메커니즘 등 핵심 원리를 코드 예제와 함께 살펴봅니다.

클래스 로딩 메커니즘: 바이트코드에서 Class 객체까지

클래스 로딩 세 단계

  1. 로딩: 바이트코드를 찾아 Class 객체 생성
  2. 링킹: 검증 → 준비 → 해석
  3. 초기화: 정적 코드 블록 실행 및 변수 초기화

사용자 정의 클래스 로더 실습

public class CustomClassLoader extends ClassLoader {
    
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        try {
            // 1. 바이트코드 파일 읽기
            String filePath = "classes/" + className.replace('.', '/') + ".class";
            byte[] byteCodes = Files.readAllBytes(Paths.get(filePath));
            
            // 2. 바이트 배열을 Class 객체로 변환
            return defineClass(className, byteCodes, 0, byteCodes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }
    }

    public static void main(String[] args) throws Exception {
        CustomClassLoader loader = new CustomClassLoader();
        Class<?> loadedClass = loader.loadClass("com.example.DynamicClass");
        
        // 3. 리플렉션으로 인스턴스 생성 및 메서드 호출
        Object instance = loadedClass.getDeclaredConstructor().newInstance();
        loadedClass.getMethod("execute").invoke(instance);
    }
}

메모리 모델: 데이터 저장 영역 다섯 가지

JVM 메모리 구조

영역 설명 스레드 공유
힙(Heap) 객체 인스턴스 저장 O
스택(Stack) 메서드 호출과 지역 변수 X
메소드 영역 클래스 정보, 상수 풀 O
네이티브 메소드 스택 네이티브 메서드 호출 X
프로그램 카운터 현재 명령어 주소 X

힙 메모리 분석

힙 메모리

  • 대상별 구조
  1. 신생대(Young Generation): 새로운 객체는 Eden 영역에 우선 할당되며, Minor GC 후 생존 객체는 Survivor 영역으로 이동합니다(나이 증가). 기본적으로 15번 이상 GC를 겪으면 Old Generation으로 승격됩니다.
  2. 노년대(Old Generation): 장기적으로 살아남은 객체를 저장하는 영역입니다. Full GC(표시-정리 알고리즘)가 발생합니다.
  • 메모리 설정 파라미터
-Xms512m  // 초기 힙 크기
-Xmx2048m // 최대 힙 크기
-XX:NewRatio=2  // 노년대:신생대 비율 2:1
-XX:SurvivorRatio=8 // Eden:Survivor 비율 8:1

비힙 메모리

영역 저장 내용 예외 시나리오 JDK 버전 변화
메소드 영역 클래스 메타정보, 상수 풀 OutOfMemoryError: Metaspace JDK 8+에서 PermGen 대체
가상 머신 스택 스택 프레임(지역 변수 테이블, 연산자 스택) StackOverflowError 변경 없음
프로그램 카운터 현재 스레드의 명령어 주소 유일한 OOM 없는 영역 변경 없음

스택 프레임 구조

public class StackFrameExample {
    public static void main(String[] args) {
        int x = 10;          // 지역 변수 테이블
        int y = 20;
        int total = add(x, y); // 메서드 호출 → 새로운 스택 프레임
        
        // 연산자 스택 작업 예시
        int z = x + y * 2;   // 연산자 스택 계산
    }
    
    static int add(int a, int b) {
        return a + b;        // 반환 값은 호출자 스택 프레임에 전달
    }
}

클래스 로딩 메커니즘과 부모 위임 모델

  1. 클래스 로딩 과정
  • 검증 단계: 바이트코드의 유효성을 확인(예: 매직 넘버 CAFEBABE).
  • 준비 단계: 정적 변수에 메모리 할당 및 초기값 설정(예: int 타입 변수는 0).
  1. 부모 위임 모델
// ClassLoader.loadClass() 핵심 로직
protected Class<?> loadClass(String className, boolean resolve) {
    synchronized (getClassLoadingLock(className)) {
        Class<?> cls = findLoadedClass(className); // 이미 로딩된 클래스 검사
        if (cls == null) {
            if (getParent() != null) {
                cls = getParent().loadClass(className, false); // 부모 로더 위임
            } else {
                cls = findBootstrapClassOrNull(className); // 부트스트랩 클래스 로더
            }
            if (cls == null) {
                cls = findClass(className); // 직접 로딩
            }
        }
        return cls;
    }
}

가비지 컬렉션 메커니즘과 알고리즘

  1. GC 트리거 조건
GC 종류 트리거 조건 적용 범위
Minor GC Eden 영역 부족 신생대만
Full GC 노년대 또는 메소드 영역 부족, System.gc() 호출 전체 힙 + 메소드 영역
  1. 알고리즘 비교
알고리즘 동작 방식 사용 시나리오 단점
복사 알고리즘 생존 객체를 Survivor 영역으로 복사 신생대(Eden) 50% 메모리 낭비
마크-스윕 알고리즘 마킹 후 직접 제거 노년대(CMS) 메모리 파편화
마크-컴팩트 알고리즘 마킹 후 메모리 정리 노년대(G1) 시간 소모 큼

실행 엔진: 바이트코드에서 기계어로

JIT 컴파일 최적화 예제

public class JITOptimizationDemo {
    // 핫 코드 감지 임계치: -XX:CompileThreshold=10000
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        
        // 핫 코드 (반복 횟수가 CompileThreshold를 초과하면 JIT 컴파일이 트리거됨)
        for (int i = 0; i < 15000; i++) {
            compute(i);
        }
        
        System.out.println("소요 시간: " + (System.nanoTime() - startTime) + "ns");
    }
    
    static int compute(int num) {
        // 복잡한 계산 (JIT 내부에서 최적화 가능)
        return num * num + 2 * num + 1;
    }
}

분할 컴파일 전략

레벨 컴파일러 최적화 수준 시작 속도
0 해석 실행 없음 가장 빠름
1 C1 간단 컴파일 기본 빠름
2 C1 부분 최적화 중간 중간
3 C1 완전 최적화 전체 느림
4 C2 심층 최적화 적극적 가장 느림

실무 적용 및 성능 최적화

공통 성능 문제 해결

  1. 메모리 누수 감지
jmap -dump:format=b,file=heap_dump.bin <pid>
jhat heap_dump.bin # 힙 덤프 분석
  1. GC 최적화 파라미터
-XX:+UseG1GC 
-Xms4g -Xmx4g 
-XX:MaxGCPauseMillis=200
  1. JIT 진단
-XX:+PrintCompilation # 컴파일 로그 출력
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining    # 내부 최적화 결정 보기
  1. CPU 사용률 급증
top -Hp <pid>  # CPU 사용량이 높은 스레드 확인
jstack <pid> | grep <nid>  # 스레드 ID 변환 및 스택 정보 확인

JVM 모니터링 도구 매트릭스

도구 역할 주요 명령
jstat GC 통계 jstat -gc 1000
jstack 스레드 스냅샷 jstack -l
jvisualvm 그래픽 모니터링 내장 프로파일러
Arthas 온라인 진단 watch demo.MathGame primeFactors

태그: JVM java 클래스로딩 메모리관리 가비지컬렉션

6월 23일 21:35에 게시됨