공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/Language

JVM의 메모리 구조 및 할당과정

inspirit941 2020. 10. 13. 20:11
반응형

JVM 구조

jvm-3

JVM은 다섯 가지 컴포넌트로 구성되어 있다.

  • 클래스 로더 시스템 : 컴파일 결과로 만들어진 .class 바이트코드 파일을 읽어들여 메모리에 배치.
    • 로딩, 링크, 초기화 세 가지 과정을 거친다.
  • 메모리
  • Runtime Engine: 바이트코드를 읽어들이는 인터프리터가 작동하는 영역.
    • 바이트 코드를 기계어로 변환하면서 Line by Line 실행하는 방식.
    • 여기 인터프리터가 기계어 코드를 실행할 때, 한 번 변환한 바이트코드를 또 변환하는 대신

      실행한 코드를 저장하는 영역이 있다. 그게 Code Cache (JIT Compiler라고도 부른다). 프로그램 실행속도를 향상시키는 용도.
    • Garbage collection.
  • Native Method interface
  • Native Method library

참고자료

JVM 메모리 구조

jvm Memory

JVM의 메모리 구조는 크게 Heap Memory, Thread Stacks, Meta Space, Code Cache, Shared Library 다섯 개로 나뉜다.

  1. Heap Memory
    • JVM이 객체의 인스턴스나 동적 할당된 데이터를 저장하는 공간.
    • Garbage Collection이 일어나는 장소.

Garbage Collection을 위해 메모리를 Young과 Old라는 두 개의 영역으로 논리적으로 분할해 사용한다.

자세한 내용은 Garbage Collection에서 다룰 예정이지만, 간단히 정리하면

  • Young : 인스턴스를 처음 생성했을 때 메모리에 배정되는 영역. 이곳에서 일어나는 Garbage Collection을 Minor GC라고 부른다.

    • Eden : 인스턴스를 생성하면 우선 이곳에 배정된다.
    • Survivor space : Minor GC로 Garbage를 걸러내고 살아남은 인스턴스가 배정된다.
  • Old : Minor GC를 여러 번 거치고도 살아남은 인스턴스가 배정되는 곳. 이곳에서의 Garbage Collection은 Major GC라고 부른다.

  1. Thread Stack

메모리에서 흔히 말하는 Stack. 스레드마다 하나씩 배정되는 메모리 영역으로, 메소드의 결과 / 반환값을 저장하거나 지역변수를 저장하는 용도로 쓰인다.

  1. MetaSpace

애플리케이션의 클래스나 메서드 정보, 또는 static으로 정의된 멤버 변수가 저장된 영역. 이전 버전에서는 PermGem이라는 이름으로도 통용된다.

  1. Code Cache

Just In time (JIT) 컴파일러가 데이터를 저장하는 영역으로, 자주 접근하는 '컴파일된 코드 블록'이 저장된다. 일반적으로 JVM은 바이트 코드를 기계어로 변환하는 작업을 수행하는데, 이곳에 저장된 코드는 기계어로 이미 변환된 채 캐시되어 있으므로 빠르게 실행할 수 있다.

 

  1. Shared Library

애플리케이션에서 사용할 공유 라이브러리가 기계어로 변환된 채 저장된 영역. 해당 OS에서 프로세스당 한 번씩 로드된다.


JVM 메모리 할당 과정 (Stack, Heap)

아래의 자바 코드를 실행하면, 프로그램 실행 과정에서 JVM 메모리에 어떤 변화가 있는지 아래의 슬라이드를 참고해 확인해보자.

class Employee {
    String name;
    Integer salary;
    Integer sales;
    Integer bonus;

    public Employee(String name, Integer salary, Integer sales) {
        this.name = name;
        this.salary = salary;
        this.sales = sales;
    }
}

public class Test {
    static int BONUS_PERCENTAGE = 10;

    static int getBonusPercentage(int salary) {
        int percentage = salary * BONUS_PERCENTAGE / 100;
        return percentage;
    }

    static int findEmployeeBonus(int salary, int noOfSales) {
        int bonusPercentage = getBonusPercentage(salary);
        int bonus = bonusPercentage * noOfSales;
        return bonus;
    }

    public static void main(String[] args) {
        Employee john = new Employee("John", 5000, 5);
        john.bonus = findEmployeeBonus(john.salary, john.sales);
        System.out.println(john.bonus);
    }
}

  1. 메인함수가 먼저 Thread Stack에 자리잡고, 그 후 첫줄 코드가 실행된다.

Employee john = new Employee("John", 5000, 5);

  1. Employee라는 새 인스턴스가 Heap에 생성되고, Employee의 생성자에 주어진 인자값으로 인스턴스의 attribute가 초기화된다.
  2. Thread Stack에 정의된 john이라는 변수가 인스턴스를 참조한다.

john.bonus = findEmployeeBonus(john.salary, john.sales);

  1. findEmployeeBonus 메소드가 Stack에 올라가고, findEmployeeBonus 메소드 내에서 getBounsPercentage 메소드가 Stack위에 쌓인다.
  2. getBonusPercentage 메소드에서 연산이 끝나면, findEmployeeBonus 메소드에 반환한 뒤 스택에서 빠져나간다.
  3. findEmployeeBonus 메소드에셔 연산이 끝나면, Integer 인스턴스가 Heap 내에 생성되고 john의 bonus라는 attribute가 Integer 인스턴스를 참조한다.

System.out.println(john.bonus);

  1. john 인스턴스의 bonus attribute를 콘솔에 출력한다.
  2. 메인함수가 void를 리턴하고 프로그램이 종료된다.

확인할 수 있는 특징은

  • primitive 데이터 타입 (int)는 stack 메모리에 바로 저장된다.
  • Integer, Employee, String과 같은 객체는 Heap에 인스턴스 형태로 저장되며, stack에서 포인터 형태로 참조한다.
  • 메소드를 호출하면 stack 위에 쌓이며, 메소드가 연산을 마치면 stack을 빠져나온다.
  • 메소드에 정의한 지역변수, 리턴값은 stack에 저장되며, 연산을 마치면 stack과 함께 사라진다.

참고자료

https://deepu.tech/memory-management-in-jvm/

반응형