Computer Sience/Java

JVM Run Time Data Area

제우제우 2024. 5. 17. 13:30

목차

  • JVM(Java Virtual Machine) 실행 데이터 영역
  • 애플리케이션이 어떻게 실행되는가?
  • Stack 메모리
  • Heap 메모리
  • CPU는 어떻게 스택 메모리에서 데이터를 찾을까?
  • 메서드 호출과 this
  • 쓰레기 객체(Garabage Object)

JVM(Java Virtual Machine) 실행 데이터 영역


메서드 영역(Method Area)

  • 클래스 메타데이터, 상수, 정적 변수 저장 
  • 모든 스레드가 공유 
  • 클래스 로더(Class Loader)에 의해서 로드된다. 

힙(Heap)

  • 모든 객체와 배열이 할당되는 메모리 영역 
  • 모든 스레드가 공유

스택(Stack)

  • 각 스레드 마다 독립적인 스택을 가진다.
  • 메서드 호출 시 생성되는 스택 프레임이 여기에 저장 
  • 각 스택 프레임은 지역 변수, 매개 변수, 리턴 주소, 이전 스택 프레임의 BP(BasePointer)를 저장 

PC 레지스터(Program Counter Register)

  • 현재  실행 중인 명령어의 주소를 가리킨다. 
  • 각 스레드 마다 하나씩 존재 

네이티브 메서드 스택(Native Method Stack)

  • 자바 외의 언어로 작성된 메서드가 실행될 때 사용 

애플리케이션이 어떻게 실행되는가?

  • 애플리케이션 : 일반 사용자가 사용할 기능을 제공하는, 컴퓨터가 실행할 수 있는 명령어들의 집합
  • 메모리 : 실행된 애플리케이션이 상주하는 곳
  • CPU : 명령어를 실행하는 주체
  • runtume : 애플리케이션이 메모리에 올라가서 실행되는 시간을 의미(실행 상태)
public class Main {
    public static void main(String[] args) {
        int a = 7;           // 1
        int b = 3;           // 2
        int c = a + b;       // 3
    }
}
  1. cpu 메모리에 a = 7 저장
  2. cpu 메모리에 b = 3 저장
  3. cpu 메모리에서 a, b를 가져와 a + b를 연산하고 해당 값을 메모리 c에 저장

Stack 메모리

public class Main {
    public static void main(String[] args) {
        int a = 100;
        a = wow(a);
    }
    public static int wow(int num){
        int b = num * 4;
        return b;
    }
}
 
main 메서드 실행 
|            |
|            | 
|------------|  <- main method 스택 프레임           
|  100       |  a
|------------| 
   stack

 

wow 함수 실행

|------------|  <- wow method 스택 프레임  
| return주소 |  <- main 메서드의 a 주소를 가진다.
|------------|
|  400       |  <-b  
|------------|             |
|  100       |  <-num  
|------------|  <- main method 스택 프레임           
|  100       |  <- a
|------------| 

 

wow 함수 종료

|            |
|            | 
|------------|  <- main method 스택 프레임           
|  400       |  a
|------------| 

CPU는 어떻게 스택 메모리에서 데이터를 찾을까?

먼저 함수(메서드)를 호출을 하면 스택 프레임이 생성된다. 

 

스택 프레임 구성 

  • 매개변수 : 함수에 전달된 인자들
  • 로컬변수 : 함수 내에서 선언된 함수들 
  • 리턴주소 : 함수 호출이 끝난 후 돌아갈 주소
  • 저장된 프레임 포인터(베이스 포인터) : 이전 스택 프레임의 시작 주소를 가리킨다. 
public class Main {
    public static void main(String[] args) {
        int a = 100;
        a = wow(a);
    }
    public static int wow(int num){
        int b = num * 4;
        return b;
    }
}

 

1. main 함수 호출

  • main 함수가 호출되면, 스택에 main 함수의 스택 프레임이 생성된다.
  • main 함수의 스택 프레임에는 args 와 a가 저장

2. wow 함수 호출 

  • main 함수에서 wow(a)가 호출될 때 새로운 프레임 생성 
  • 이 프레임에는 매개변수 num값과 로컬 변수 b가 저장 

cpu는 스택 포인터(Stack Pointer, SP), 베이스 포인터(Base Pointer, BP)라는 레지스터를 사용하여 스택 메모리를 관리

 

스택 포인터

  • 현재 스택의 최상단을 가리키는 레지스터
  • 함수 호출 시 새로운 스택 프레임이 추가되면, 스택 포인터가 그 위치로 이동 
  • 함수가 종료되면, 스택 포임터가 이전의 위치로 돌아간다. 

베이스 포인터

  • 현재 함수의 스택 프레임의 시작 지점을 가리킨다.
  • 로컬 변수나 매개변수에 접근할 때, 베이스 포인터를 기준으로 오프셋을 계산하여 접근한다. 
| ...        |
|------------| <--- SP
| 로컬 변수  |  <- `main` 함수의 로컬 변수 (예: a)
| 매개변수   |  <- `main` 함수의 매개변수 (예: args)
|------------| <--- BP (main 함수의 시작 지점)
| ...        |
|------------| <--- SP
| 로컬 변수 b|  <- `wow` 함수의 로컬 변수
| 매개변수 num|  <- `wow` 함수의 매개변수
| 이전 BP   |  <- `main` 함수의 BP
|------------| <--- BP (wow 함수의 시작 지점)
| 로컬 변수  |  <- `main` 함수의 로컬 변수
| 매개변수   |  <- `main` 함수의 매개변수
|------------| <--- BP (main 함수의 시작 지점)
| ...        |
|------------| <--- SP
| 로컬 변수  |  <- `main` 함수의 로컬 변수
| 매개변수   |  <- `main` 함수의 매개변수
|------------| <--- BP (main 함수의 시작 지점)

 

 


Heap 메모리

public class Main {
    public static void main(String[] args) {
         Counter c = new Counter();
    }
}
class Counter{
    private int state = 0;
    public void increment(){ state++; }
    public int get() {return  state; }
}
 

main 스택 프레임 생성

new Counter() 실행 - 새로운 스택 프레임


class Counter{
    //Counter(){}
    private int state = 0;
    public void increment(){ state++; }
    public int get() {return  state; }
}

 

Counter 생성자가 없으니까 컴파일 시점에 자동으로 매개변수가 없는 기본 생성자가 생성 해당 메서드에는 지역변수, 매개변수가 없으니까 스택 프레임에도 해당 변수들이 없지만 만약 매개변수가 있다면 이 변수들도 스택 프레임에 추가된다.

 

ex)

public class Main {
    public static void main(String[] args) {
         Counter c = new Counter(10);
    }
}
class Counter{
    Counter(int state){
        this.state = state;
    }
    private int state = 0;
    public void increment(){ state++; }
    public int get() {return  state; }
}
 

메서드 호출과 this

자바에서 인스턴스 메서드를 호출하면, JVM은 메서드 호출을 다음과 같은 방식으로 처리한다.

 

1. 객체 식별

  • 메서드 호출이 발생하면 해당 객체의 레퍼런스가 메서드에 암묵적으로 전달됩니다. 이 레퍼런스가 바로 this 이다.
  • 예를 들어, c.increment() 를 호출할 때, c 객체의 레퍼런스가 increment 메서드로 전달된다.

2. 스택 프레임 생성

  • 메서드 호출 시 새로운 스택 프레임 생성
  • 이 스택 프레임에는 메서드의 지역 변수와 매개변수들이 저장
  • 인스턴스 메서드의 첫 번째 매개변수는 항상 this로, 호출된 객체의 래퍼런스를 가리킨다.

3. 메서드 실행

  • 메서드 내에서 인스턴스 변수나 다른 메서드에 접근할 때, this를 사용하여 호출된 객체의 멤버에 접근한다.
  • 예를 들어 state++는 실제로 this.state++ 

예제 코드 

public class Main {
    public static void main(String[] args) {
         Counter c = new Counter();
         c.increment();
    }
}
class Counter {
    private int state = 0;

    public void increment() {
        state++;  // 실제로는 this.state++
    }

    public int get() {
        return state;  // 실제로는 this.state
    }
}

 

결론

자바에서는 인스턴스 메서드를 호출할 때, 해당 객체의 레퍼런스가 암묵적으로 this로 전달

이를 통해 메서드는 객체의 인스턴스 변수를 식별하고 조작할 수 있다. 따라서 매개변수로 명시적으로 객체를 전달하지 않아도, 메서드 내부에서 객체를 식별할 수 있습니다.


쓰레기 객체(Garabage Object)

public class Main {
    public static void main(String[] args) {
         Counter c = make();
    }
    public static Counter make(){
        Counter c = new Counter(); // 쓰레기 객체 
        return new Counter();
    }
}
class Counter{
    private int state = 0;
    public void increment(){ state++; }
    public int get() {return  state; }
}

 

해당 객체는 더이상 접근할 방법(래퍼런스)이 없다

이런 접근 불가능한 객체를 쓰레기 객체라고 한다.

→ GC(garbage collector)가 해결한다.

 

GC의 동장 방식에 따라 애플리케이션의 성능이 크게 바뀐다.