자바 프로그램에서 코드가 컴파일되어 JVM에서 실행되기까지 과정에서 JVM은 필요한 데이터들을 용도에 따라 나누어 관리한다.
JVM의 메모리 공간은 크게 Method 영역, Stack 영역, Heap 영역으로 구분되고 데이터 타입(자료형)에 따라 각 영역에 나눠서 할당된다.
메서드 영역(Method Area)
메서드 영역은 JVM이 시작될 때 생성되는 곳으로 클래스 변수(Static 변수), 생성자(constructor)와 메서드(method) 등이 저장되는 공간이다.
- Static 영역이라고도 불리며 어느 곳에서나 접근이 가능하다.
- 모든 스레드에서 공유되어 멀티 스레드 환경에서 동기화에 주의해야 한다.
- 메서드 영역의 데이터는 프로그램이 종료될 때까지 메모리에 남아있다.
그래서 프로그램이 종료될 때 까지 어디서든 사용이 가능하다. - 무분별하게 많이 저장할 경우 메모리 부족 현상이 일어날 수 있다.
public class Car {
private static final int NAME_LENGTH_COND = 5;
private final String name;
private int position;
public Car(final String name, final int position) {
validateNameLength(name);
this.name = name;
this.position = position;
}
public void move() {
position++;
}
private void validateNameLength(final String name) {
if (name.length() > NAME_LENGTH_COND) {
throw new IllegalArgumentException("이름은 5글자까지만 가능합니다.");
}
}
}
이 코드에서는 클래스 변수(NAME_LENGTH_COND
), 생성자(Car
), 메서드(move()
, validateNameLength()
)가 메서드 영역에 저장된다.
스택(Stack)
스택 영역은 메서드 호출 시 생성되는 기본 자료형인 지역 변수와 매개 변수가 저장되는 영역이다.
- 각 스레드마다 독자적인 스택 영역을 가진다.
- 스택 프레임(Stack Frame) 단위로 메모리를 관리한다.
- 메서드가 하나 호출될 때마다 스택 프레임이 생성되고 메서드가 끝나거나 예외가 터지면 제거된다.
- 이 스택이 가득 찼을 때 발생하는 오류가
StackOverFlowError
이다. (재귀 호출)
스택 프레임이란?
하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임(Stack Frame)이라고 한다.
메서드를 호출하기 직전 스택 프레임을 스택영역에 생성한다.
위 코드의 Car 객체를 생성하고 move() 메서드를 실행한다면 3덩어리의 스택 프레임이 생성된다.
메서드가 호출될 때마다 생성되므로 Car
생성자 -> validateNameLength()
-> move()
순서대로 쌓이고 메서드 실행이 끝나면 해당 스택프레임은 스택에서 제거된다.
name, position이 왜 저장돼?라고 생각할 수 있다. 그건 힙 스택 이후에 확인해 보자
힙(Heap)
힙 영역은 new 키워드를 통해 생성된 참조형 객체와 인스턴스 변수 그리고 배열이 저장된다.
- 메서드 영역과 동일하게 모든 스레드에서 공유되는 곳이다.
- 단, 힙 영역에 있는 객체들을 가리키는 참조를 위한 주소값은 스택에 포함된다.
- 힙 영역은 스택 영역과 다르게 보관된 데이터가 호출이 끝나더라도 삭제되지 않고 유지된다.
- 더 이상 참조하지 않는 객체 데이터는 가비지 컬렉터(GC)에 의해 제거된다.
public class Main {
public static void main(String[] args) {
Car car = new Car("kaki", 0);
car.move();
}
}
이 코드를 실행해 보자.
1. Car car = new Car("kaki", 0);
스택 영역에 생성자 메서드의 스택 프레임이 생성되고Car 객체에 대한 주소값을 가진다.
힙 영역에 Car 클래스의 인스턴스 변수, 값이 저장되고 스택 영역의 해당 주소값과 연결된다.
name과 position은 지역변수/매개변수가 아니라 인스턴스 변수인데 왜 스택에 저장되는가?
스택 영역의 name
과 position
은 실제 값이 아닌, 메서드의 매개변수와 참조가 잠시 저장되는 것이다.
public Car(final String name, final int position) {
validateNameLength(name);
this.name = name;
this.position = position;
}
즉, Car
생성자 호출 시에, 생성자에 전달되는 매개변수 name
과 position
(즉, "CarName"
과 0
)은 생성자 호출 시 스택 영역에 저장되는 것이다.
name
과 position
은 매개변수로서 단순히 메서드 호출 동안에만 잠시 스택에 존재하는 것일 뿐이고 메서드 실행이 끝나면 스택에서 사라진다.
2. car.move();
생성자 호출이 끝났으니 Car 생성자 스택 프레임과 validateNameLength()
메서드 스택 프레임은 제거되고, mova()
메서드 스택 프레임이 추가된다.
여기서 this
라는 암묵적인 변수가 자동 생성되게 되는데, 이 this
변수는 자동으로 힙 영역에 있는 Car
객체를 가리키게 된다.
그래서 move()
메서드 안의 코드 position++
이 동작하면 힙 영역에 있는 인스턴스 변수 position
값이 변하게 된다.
3. main() 메서드 종료
move() 메서드가 종료되고 main() 메서드 호출이 끝나면 main() 스택 프레임이 스택 영역에서 제거된다.
힙 영역의 Car 객체 데이터는 더 이상 참조하는 곳이 없을 때 GC에 의해 제거된다.
왜 각 종류의 변수들은 해당 메모리에 할당될까?
각 영역을 알아봐도 헷갈려서 따로 정리하게 됐다.
public class Calculator {
int a = 1; //인스턴스 변수
static int b = 3; //클래스 변수
public static void main(String[] args) { //매개변수
Calculator cal = new Calculator();
int c = 3; //지역변수
}
}
각 영역이 뭔진 알겠는데
왜 클래스 변수는 메서드 영역에 저장되고, 인스턴스 변수는 힙 영역에 저장되고, 매개변수/지역변수는 스택에 저장될까?
인스턴스 변수(a)가 힙 영역에 저장되는 이유
생성 시기
: new Calculator()
로 새로운 Calculator
객체를 생성할 때 a
라는 인스턴스 변수가 생성된다.
왜 힙에 저장되는가?
인스턴스 변수는 각 객체마다 고유한 값을 가지므로 객체가 생성될 때마다 독립적으로 생성되어야 한다.
힙은 객체가 메모리에 할당되어 더 이상 참조되지 않을 때까지 제거되지 않고 유지된다.
인스턴스 변수는 객체와 함께 힙 영역에 저장되어 해당 객체가 참조될 때마다 접근할 수 있어야 하기 때문이다.
클래스 변수 (b)가 메서드 영역에 저장되는 이유
생성 시기
: 클래스 변수는 Car 클래스가 처음 메모리에 로드될 때 한 번 생성된다.
왜 메서드 영역에 저장되는가?
클래스 변수는 모든 인스턴스가 공유하는 값이다 즉, 여러 객체가 생성되더라도 단 하나의 값을 유지한다.
메소드 영역은 프로그램 종료 시까지 유지되므로 클래스 변수의 값도 프로그램 종료 시까지 유지되어야 하기 때문이다.
매개변수 (args), 지역 변수 (c)가 스택 영역에 저장되는 이유
생성 시기
: 매개변수는 메서드가 호출될 때 생성된다.
왜 스택에 저장되는가?
매개변수와 지역변수는 메서드나 코드 블록 내에서만 사용되고, 해당 코드 블록이 끝나면 더 이상 참조되지 않는 임시 데이터입니다.
스택은 메소드 호출 시 생성되는 지역 데이터의 임시 저장을 위해 사용되고, 두 변수의 데이터는 메서드가 끝나면 필요 없어지므로 스택에서 관리된다.
참고
JVM 메모리 구조 파헤쳐 보기 (Static, Stack, Heap)
그림으로 보는 자바 코드의 메모리 영역(스택 & 힙)
'💻 Dev > Java' 카테고리의 다른 글
String Constant Pool, feat. Runtime Constant Pool (0) | 2024.11.02 |
---|---|
BigDecimal이란? (0) | 2024.10.30 |
Immutable(불변성), StringBuffer와 StringBuilder (0) | 2024.10.29 |
JVM 구조와 동작 과정 (0) | 2024.10.29 |
예외(Checked Exception, Unchecked Exception) (0) | 2024.10.26 |
interface와 abstract(추상) 클래스 (0) | 2024.10.25 |