본문 바로가기
Java

[Java] Java 메모리 구조 (JVM, Stack, Heap, Static)

by 지지 2024. 1. 30.

Java의 메모리구조를 이해하기 위해서는 JVM을 어느정도 알아야한다.

사실 JVM의 메모리구조라고 하는 것이 맞을수도 있겠다.

본격적인 시작 전에 JVM의 동작 과정을 간단하게 설명해보겠다.


사전지식1 (JVM의 구조와 동작 과정)

먼저, 우리가 java 언어로 작성한 코드가 어떻게 프로그램 위에서 돌아가는지를 알아야한다.

 

1. 개발자가 코딩을 하면 .java 파일이 생성이 된다.

2. .java 파일은 자바컴파일러(javac)에 의해 .class 파일(바이트 코드)로 컴파일된다.

3. .class 파일은 JVM의 ClassLoader에 의해 Runtime Data Area에 로드된다.

4. Runtime Data Area에 로드된 .class들은 Execution Engine을 통해 해석된다.

    (**이 때, 인터프리터 방식과 JIT컴파일러 방식 두 가지 방식을 모두 사용한다.)

5. 해석된 .class 는 Runtime Data Area의 각 영역에 적절하게 배치되어 수행된다.

6. 마지막으로 Garbage Collector는 더 이상 사용하지 않는 메모리를 자동으로 회수해준다.

 

JVM 동작과정

 

간략하게 설명한 java의 실행과정이다.

이 중 해당 포스팅에서 다룰 내용은 5번의 'Runtime Data Area의 각 영역에 적절하게 배치되어 수행' 되는 과정이다.

 

컴퓨터의 메모리는 사용할 수 있는 공간이 한정되어 있기 때문에 어떻게 관리하느냐에 따라 프로그램의 성능이 좌우된다.

Runtime Data Area는 JVM이 운영체제로부터 할당받은 메모리 영역이기 때문에 해당 영역을 잘 관리해줄 필요가 있다.


사전지식2 (Java 변수의 종류)

메모리영역에 올라가는 것들 중 설명과 이해가 가장 쉬운 것이 변수라고 생각된다.(실제로 많은 블로그들도 변수로 설명하고 있기 때문에)

java에서는 변수를 어디서 선언했느냐에 따라 각 영역에 올라가는 데이터가 달라진다.

 

먼저, 선언 위치에 따른 변수의 종류를 살펴보자.

public class Test{
    /* 클래스 영역 */
    public int a = 0; //인스턴스 변수
    public static int b = 0; //클래스 변수
    
    public void test(int c){ //매개변수
        /* 메서드 영역 */
    	int d = 0; //지역변수
    }
}

 

java에서 변수는 선언 위치에 따라 네 가지로 구분할 수 있다.

변수명 설명 생성 시기 소멸 시기 저장 메모리
클래스 변수
(= static 변수)
클래스 영역에서 타입 앞에 static이 붙는 변수
객체에서 공통으로 사용하고자할 때 사용
클래스가 메모리에 올라갈 때 프로그램 종료 시 Method Area (=Static Area)
인스턴스 변수 클래스 영역에서 static이 아닌 변수
개별적인 저장공간으로 객체/인스턴스마다 다른 값 저장 가능
인스턴스가 생성될 때 인스턴스 소멸 시 Heap Area
지역변수 메서드  내부에 선언된 변수
초기값을 지정해야 사용가능함
메서드 내에서 변수가 선언될 때 블록을 벗어날 때 Stack Area
매개변수 메서드 호출시 전달하는 값을 가지고 있는 변수 메서드가 호출 될 때 블록을 벗어날 때 Stack Area

메모리 구조

위 사전지식 파트에서 모두 언급된 것이 바로 JVM의 메모리 영역이다.

이제 이 메모리영역에 대해서 자세히 다뤄보도록 하겠다.

 

Runtime Data Area는 크게 다섯 영역으로 나뉜다.

1. Method Area (정적영역, Static Area)

2. Heap Area (힙 영역)

3. Stack Area (스택 영역)

4. PC register

5. Native Method Stack


1. Method Area (=Static Area)

JVM이 동작해서 클래스가 로딩될 때 생성되며, static이 붙은 변수나 메서드의 정보를 저장하는 공간이다.

모든 쓰레드가 공유하는 영역이기 때문에 다음과 같은 초기화 코드 정보들이 저장되게 된다.

  • Field Info : 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보
  • Method Info : 메소드 이름, return 타입, 접근 제어자 정보
  • Type Info : Class 인지 Interface 인지 여부 저장, Type의 속성, 이름 Super Class의 이름

즉, static이 붙은 변수와 메서드는 클래스가 로딩될 때 바로 메모리영역에 올라가므로 객체가 생성되기 이전에 이미 할당이 되게된다. 그렇기 때문에 객체의 생성 없이 바로 접근하여 사용이 가능한 것이다.

public class TestCal {
    public static int add(int x, int y) {
        return x + y;
    }

    public int min(int x, int y) {
        return x - y;
    }
}

class TestCal2 {
    public void cal2() {
        TestCal.add(1, 2);   //  static 메소드이므로 객체 생성 없이 사용 가능
        TestCal.min(1, 2);   //  [error] static 메소드가 아니므로 객체 생성후에 사용가능

        TestCal cal = new TestCal();
        cal.add(1, 2);   // o 가능은 하지만 권장하지 않음
        cal.min(1, 2);   // o
    }
}

2. Heap Area

힙 영역은 메서드 영역와 함께 모든 쓰레드가 공유하며, 

JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다.

참조형(Reference Type) 데이터 타입을 갖는 객체(인스턴스)나 배열 등이 저장되는 공간이다.

즉, new() 연산자를 통해 생성되는 클래스와 인스턴스 등을 저장한다.(class, interface, enum, Array ...)

 

힙 영역에 생성된 객체와 배열은 Reference Type으로써, 스택 영역의 변수나 다른 객체의 필드에서 참조된다.

즉, 힙의 참조 주소는 스택이 갖고있고, 해당 객체를 통해서만 힙 영역에 있는 인스턴스를 핸들링할 수 있는 것이다.

스택, 힙의 관계

만일 참조 하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 이것을 쓰레기로 취급하고 Garbage Collector가 실행되어 쓰레기 객체를 힙 영역에서  자동으로 제거한다.

이처럼 힙은 Garbage Collection에 대상이 되는 공간이다.

효율적인 Garbage Collection을 수행하기 위해 힙 영역은 다시 크게 두개의 영역으로 나뉘게 되는데, 해당 설명은  Garbage Collection을 다룰 때 더 집중적으로 다루도록 하겠다.


3. Stack Area

스택 영역은 int, long, boolean 등 기본 타입의 '값'과 참조타입의 '객체주소'를 저장하는 공간으로, 임시적으로 사용되는 변수나 정보들이 저장되는 영역이다.

메서드가 호출 될 때 메모리에 할당되고, 종료되면 자동으로 메모리에서 제거된다.

그렇기에 스택은 후입선출(LIFO : Last In First Out)의 특성을 가진다.

 

메서드를 호출하면 스택영역에 스택프레임이 생기는데, 이는 현재 실행중인 메서드 상태 정보를 저장하는 곳이다.

하나의 메서드 당 하나의 스택프레임이 필요하며, 메서드를 호출하기 직전에스택프레임을 Stack에 생성한 후 메서드를 호출한다.

스택프레임에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴값 등이있다.

 

프로세스가 메모리에 로드 될 때 스택 사이즈가 고정되어 있어, 런타임 시에 스택 사이즈를 바꿀 수는 없다.

만일 고정된 크기의 JVM 스택에서 프로그램 실행 중 메모리 크기가 충분하지 않다면 StackOverFlowError가 발생하게 된다.

 


4. PC Register (Program Counter Register)

PC 레지스터는 쓰레드가 시작될 때 생성되며, 현재 수행중인 JVM 명령어 주소를 저장하는 공간이다.

JVM 명령의 주소는 쓰레드가 어떤 부분을 무슨 명령으로 실행해야할지에 대한 기록을 가지고있다.

프로그램의 실행은 CPU에서 명령어(Instruction)를 수행하는 과정으로 이루어지는데, 이 때 CPU는 연산을 수행하는 동안 필요한 정보를 register라고 하는 CPU 내의 기억 장치를 이용하게 된다.

java는 현재 작업하는 내용을 CPU에게 직접 연산을 수행하도록 하는 것이 아니라, CPU에게 연산으로 제공해야하므로, 이를 위한 버퍼공간으로 PC Register라는 메모리 영역을 사용하게 된다.


5. Native Method Stack

네이티브 메서드 스택은 자바 코드가 컴파일되어 생성되는 바이트 코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역이다.

또한 자바 이외의 언어(C, C++, 어셈블리)로 작성된 네이티브 코드들을 실행하기 위한 공간이기도 하다.

사용되는 메모리 영역으로는 일반적인 C 스택을 사용한다.

위에서 잠깐 언급한 JIT 컴파일러에 의해 변환된 Native Code 역시 여기에서 실행된다고 보면 된다.


Heap과 Stack 비교

1. 힙은 애플리케이션의 모든 쓰레드가 공유하며, 단 하나의 영역만 가지고 있다. 스택은 각각의 쓰레드에 각각의 스택 영역을 가지고 있다. 그래서 힙에 저장된 객체는 어디서든지 접근이 가능하지만, 스택에 저장된 데이터는 다른 쓰레드가 접근할 수 없다.

2. 객체가 생성되면 객체는 힙 영역에 저장되고, 해당 객체의 주소를(참조변수) 스택이 가지고 있다.

3. 힙메모리의 생명주기는 길고(애플리케이션 시작-종료), 스택메모리의 생명주기는 짧다(메서드 호출-종료).

4. 스택의 사이즈는 힙과 비교했을 때 매우 작지만, 간단한 메모리 할당법(LIFO)을 사용하기 때문에 힙보다 빠르다.

5. 힙은 Garbage Collector의 대상이지만, 스택은 자동으로 메모리에서 제거(pop)된다.


여기까지 JVM의 메모리 구조를 간단하게 정리해보았다.

머리로만 대충 이해하고 있는 내용을 더 많이 알아보고 정리해보니 왜 진작 안했을까 하는 생각이 든다ㅠㅠ

더 자세하고 깊게 정리하는 것이 목표였는데, 생각보다 내용이 길어질 것 같고 깊은 내용이 많아서 그렇게 하지 못했다.

따로따로라도 더 공부해서 포스팅 하는 날이 오길!

반응형

댓글