[05/17] 자바의 메모리 영역과 컴파일과정

Haisley Jang·2024년 5월 17일

CS

목록 보기
4/5
post-thumbnail

JVM이란?

.java 컴파일을 하려할 때, javac이라는 Compiler가 Byte Code인 .class로 컴파일함

이 .class 파일을 처리하는 곳이 JVM이며, Class loader, Runtime Data Areas, Execution Engine을 포함함

runtime에 순환하며 일어나는 과정

  1. .class를 class loader가 가져옴
  2. runtime data area가 사용하는 변수를 메모리에 올림
  3. Execution Engine은 프로그램을 실행함

이 때, runtime data area에서 자바 어플리케이션에 사용되는 데이터를 적재함.

이는 Method Area, Heap Area, Stack Area, PC Register, Native Method Stack으로 나뉨

Runtime Data Area : Java의 메모리 영역

Method Area (Static 영역)

모든 스레드가 공유하는 메모리 영역

클래스, 인터페이스, 메소드, 속성, static 변수 등의 바이트 코드를 보관함

런타임 중에 변경될 수 없다는 특징이 있으며 고정된 공간을 차지함

Heap Area

모든 스레드가 공유하는 메모리 영역

new 키워드로 생성된 객체와 배열이 생성됨

메모리의 낮은 주소에서 높은 주소의 방향으로 데이터가 할당됨

method area에 로드된 클래스로만 객체 생성이 가능하며, garbage collector의 주요 대상임

Stack Area

메소드 호출될 때 메소드의 스택 프레임이 생성됨

스택 프레임에서 메소드 안에 사용된 원시 타입의 매개변수, 지역변수 데이터를 저장함

동적으로 생성된 객체일 경우, 힙 메모리 영역의 주소값을 저장함

메모리의 높은 주소에서 낮은 주소 방향으로 데이터가 할당됨

메소드 수행이 끝난 경우 프레임 별로 삭제됨

PC Register

쓰레드가 시작될 때 생성되며, 쓰레드마다 하나씩 존재함

현재 어떤 명령어를 실행해는 지를 기록하며, 수행중인 명령어의 주소를 가짐

Native Method Stack

자바 외 언어로 작성된 코드를 위한 메모리 영역

컴파일 과정

  1. 개발자가 .java 파일을 작성함
  2. Javac이 소스파일을 컴파일 하여 .class인 바이트 코드 파일을 생성함
  3. 컴파일된 바이트 코드를 JVM 클래스 로더로 전달함
  4. 클래스 로더는 동적 로딩을 통해 필요한 클래스만 로딩 및 링크하여 런타임 데이터 영역에 적재함
    1. 로드 : 클래스 파일을 Method Area로 로드함
    2. 검증 : Java 및 JVM 명세로 구성됐는지 검증
    3. 준비 : 클래스가 필요로 하는 메모리를 Runtime Data Area에 할당
    4. 분석 : 클래스 상수의 심볼릭 레퍼런스(상징적인 이름 사용)를 다이렉트 레퍼런스(메모리 상 실제 주소로 매핑)로 변환
    5. 초기화 : static 필드를 초기화 함
  5. 실행엔진은 PC Register에 저장된 명령어 단위로 실행함
    1. 인터 프리터 사용시 : 바이트 코드 명령어를 하나씩 읽어서 해석 → 각각의 실행은 빠르나, 전체적인 실행속도는 느림
    2. JIT 컴파일러 사용시 : 바이트 코드 전체를 바이너리 코드로 컴파일 → 인터프리팅 없이 바이너리 코드를 실행함 ⇒ 실행속도가 인터프리팅 방식보다 빠름

컴파일 방식

정적 컴파일 방식

소스 코드 (고급 언어) 전체를 기계어로 번역후 실행함

예시 : C, C++

  • 장점 : 실행 속도가 빠름
  • 단점 : 플랫폼 간 호환성이 낮음, 코드 수정 후 전체의 코드가 다시 컴파일 되어야 함

인터프리터를 사용한 실행

소스코드를 중간언어로 번역한 다음, 한줄 씩 대응하는 기계어 코드를 실행함

예시 : 파이썬, 루비 등

  • 장점 : 프로그램 수정 후 바로 결과 확인이 가능
  • 단점 : 정적 컴파일 보다 느림, 실행마다 번역 과정이 필요

JIT(Just In Time) 컴파일

정적 컴파일 + 인터프리터의 하이브리드 형식으로, 동적 번역을 사용함

실행할 때, 인터프리터 방식으로 기계어 코드를 생성함과 동시에 캐싱함

예시 : Java의 JVM

  • 장점 : 처음에는 느리지만 반복 실행으로 성능이 개선됨
  • 단점 : 초기 실행시 성능이 저하됨

실습

Main.java

public class Main {
    static int num = 1;

    public static void main(String[] args) {
        int a = 100;
        int b = 200;
        int c = a + b;
        Test t = new Test();
        c = c + t.num;
    }
}

class Test {
    int num = 90;
}

위의 코드를 javac으로 컴파일

javac Main.java 

그러면 Main.class가 생기는데 이 파일 보기

javap -c Main.class
Compiled from "Main.java"
public class Main {
  static int num;

  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        100
       2: istore_1
       3: sipush        200
       6: istore_2
       7: iload_1
       8: iload_2
       9: iadd
      10: istore_3
      11: new           #7                  // class Test
      14: dup
      15: invokespecial #9                  // Method Test."<init>":()V
      18: astore        4
      20: iload_3
      21: aload         4
      23: getfield      #10                 // Field Test.num:I
      26: iadd
      27: istore_3
      28: return

  static {};
    Code:
       0: iconst_1
       1: putstatic     #14                 // Field num:I
       4: return
}

참고자료

https://steady-coding.tistory.com/305

https://velog.io/@rik963/JAVA-JVM-구조-및-메모리-영역-Class-Heap-Stack-Area

https://www.devkuma.com/docs/jvm/memory-structure/

https://gyoogle.dev/blog/computer-language/Java/컴파일 과정.html

https://velog.io/@jeongbeom4693/컴파일러-인터프리터-JIT-컴파일

profile
안녕하세요

0개의 댓글