[Java] 메모리
Java의 메모리 영역
Java 프로그램이 실행되면 JVM(자바 가상 머신)은 OS로부터 메모리를 할당받고, 그 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
JVM의 메모리 공간(Runtime Data Area)는 크게 Method(Static) 영역, Stack 영역, Heap 영역으로 구분되고 데이터 타입에 따라 각 영역에 나눠서 할당 되게 된다.
JVM이란?
- 자바 프로그램 실행환경을 만들어주는 소프트웨어
- 자바 코드를 컴파일하여 .class 바이트 코드로 만들면 해당 코드가 JVM 환경에서 실행된다
- JVM은 JRE(Java Runtime Environment)에 포함되어 있다.
Java는 어떠한 플랫폼에 영향을 받지 않는다.
JVM을 사용하면 하나의 바이트 코드(.class)로 모든 플랫폼에서 동작하도록 할 수 있다.
.class : 바이트 코드라고 하며 사람이 쓰는 자바 코드에서 컴퓨터가 읽는 기계어의 중간 단계이다.
[예시]
C / C++에서는 컴파일 플랫폼(OS)과 타겟 플랫폼이 다를 경우 프로그램이 동작하지 않는다.
하지만 Java의 경우 플랫폼에 설치되어 있는 JVM이 운영체제에 맞는 실행 파일로 변환 시켜주기에 컴파일한 바이트 코드로 모든 플랫폼에서 동작이 가능하다.
즉 Java는 플랫폼에 종속적이지 않지만 JVM은 플랫폼에 종속적이다.
자바 변수
메모리 영역에 대한 이해도를 높이기 위해선 자바 변수의 종류에 대한 이해가 우선되어야 한다.
변수명 | 선언위치 | 생성시기 | 설명 |
---|---|---|---|
클래스 변수 (class variable) (= static 변수) | 클래스 영역 | 클래스가 메모리에 올라갈 때 | 클래스 영역에서 타입 앞에 static이 붙는 변수 객체를 공유하는 변수로 여러 객체에서 공통으로 사용하고 싶을 때 정의 |
인스턴스 변수 (instance variable) | 클래스 영역 | 인스턴스가 생성되었을 때 | 클래스 영역에서 static이 아닌 변수 개별적인 저장 공간으로 객체/인스턴스마다 다른 값 저장 가능 ※ 객체/인스턴스 생성만 하고 참조 변수가 없는 경우 가비지 컬렉터에 의해 자동 제거됨 |
지역 변수 (local variable) | 메서드 영역 | 위치하고 있는 메서드가 수행되었을 때 | 메서드 내에서 선언되고 메서드 수행이 끝나면 소멸되는 변수 초기값을 지정한 후 사용할 수 있음 |
매개 변수 (parameter) | 메서드 영역 | 위치하고 있는 메서드가 수행되었을 때 | 메서드 호출 시 ‘전달하는 값’을 가지고 있는 인수 (지역 변수처럼 선언된 곳부터 수행이 끝날 때까지 유효함) |
Method(Static) 영역
- JVM이 구동될 때 생성되며 모든 스레드가 공유하는 영역
- JVM이 읽어들인 클래스와 인터페이스에 대한 런타임 상수 풀(runtime constant pool), 멤버 변수(필드), 클래스 변수(Static 변수), 상수(final), 생성자(constructor)와 메소드(method)등을 저장하는 공간
- Method(Static) 영역의 데이터는 프로그램의 시작부터 종료될 때까지 메모리에 남아있다
- 장점 : 프로그램이 종료될 때까지 어디서든 접근 및 사용 가능
- 단점 : 무분별하게 많이 사용할 경우 메모리 부족 현상이 일어날 수 있게 된다.
* 런타임 상수 풀(runtime constant pool)
- Type에서 사용된 상수를 저장하는 곳
- 문자 상수, 타입, 필드 Method reference도 상수 풀에 저장
- final class 변수의 경우에도 상수 풀에 값 복사
[예시]
public class Main {
public static int s = 10;
public static void main(String[] args) {
int a = 5;
int b = 5;
int result1 = a + b + Main.s;
System.out.println(result); // 20
Counter sub = new Counter();
twice(sub);
int result2 = sub.get();
System.out.println(result2); // 100
}
public static void twice(Counter c) {
c.plus(10);
c.plus(20);
}
}
class Counter {
public int state = 50;
public final int count = 20;
public int get() {
return state + count;
}
public void plus(int n) {
state += n;
}
}
- 클래스 변수(static)와 메소드는 무조건 Method 영역에 적재
- 일반 인스턴스 변수인 Counter 클래스의 변수
state
그리고count
는 final 키워드가 붙어있음에도 Method 영역에 들어가지 않음
Stack 영역
- 원시(primitive) 타입의 데이터(int, double, byte, long, boolean 등)에 해당되는 지역변수, 매개 변수 데이터 값이 저장
- 메소드가 호출 될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
- 후입선출(LIFO)의 특성을 가지며 스코프(scope)의 범위를 벗어나면 메모리에서 삭제
- Stack 영역은 각 스레드가 생성되는 순간마다 해당 스레드를 위한 stack 영역이 생성된다.
- 서로 다른 스레드의 stack 영역에는 접근 불가
- 메소드가 호출될때 스택 영역에 스택 프레임이 생기고 그 안에 메소드를 호출 ```
- 스택 프레임(stack frame) 하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임(stack frame)이라고 한다. 하나의 메서드당 하나의 스택 프레임이 필요하며, 메서드를 호출하기 직전 스택 프레임을 자바 Stack에 생성한 후 메서드를 호출하게 된다. 스택 프레임에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴값 등이 있다. 만일 메서드 호출 범위가 종료되면 스택에서 제거된다. ```
[예시]
1
2
3
4
5
6
7
8
9
10
public class Main {
public static int s = 10;
public static void main(String[] args) {
int a = 5;
int b = 5;
int result1 = a + b + Main.s;
System.out.println(result); // 20
}
}
Heap 영역
- 참조형(Refenrece Type) 데이터 타입을 갖는 객체(인스턴스), 배열 등이 저장되는 공간
- 단, Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 stack에 적재
- Heap 영역은 Stack 영역과 다르게 보관되는 메모리가 호출이 끝나더라도 삭제되지 않고 유지된다. 그러다 어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게 되면, GC(가비지 컬렉터)에 의해 메모리에서 청소된다.
- Stack은 스레드 수에 맞춰 각각 생성되지만, Heap은 몇 개의 스레드가 존재하든 상관없이 단 하나의 Heap 영역만 존재한다.
실행 과정
[예시]
public class Main {
public static int s = 10;
public static void main(String[] args) {
int a = 5;
int b = 5;
int result1 = a + b + Main.s;
System.out.println(result); // 20
Counter sub = new Counter();
twice(sub);
int result2 = sub.get();
System.out.println(result2); // 100
}
public static void twice(Counter c) {
c.plus(10);
c.plus(20);
}
}
class Counter {
public int state = 50;
public final int count = 20;
public int get() {
return state + count;
}
public void plus(int n) {
state += n;
}
}
1. Method 영역 확인
2. Counter 생성자 호출
3. twice 메소드 실행
4. plus 메소드 실행
- 객체
Counter
에 정의된 새로운 메소드를 실행하는 것이기에 새로운 스택 프레임 생성 this
라는 암묵적인 변수 자동 생성, 이this
는 자동으로 Heap 영역의Counter
를 가리키게 된다.- 연산이 이루어져 Heap 영역의 인스턴스 변수
state
값이 변환된다
5. get 메소드 실행
- 실행 완료한
plus
스택 프레임 제거 get
이라는 새로운 메소드를 실행했기에 새로운 스택 프레임 생성this
변수가 Heap 영역의 객체를 가리키고 해당 객체의 인스턴스 변수를 반환- 실행 완료한
get
스택 프레임 제거 - 지역 변수
result2
가 main 스택 프레임에 추가
6. 마지막 코드 실행 후 main 스택 프레임 제거
7. GC(가비지 컬렉터) 청소 시간
[추가] Heap과 Stack 메모리의 차이점
- Heap 메모리는 어플리케이션의 모든 부분에서 사용되는 반면, Stack 메모리는 하나의 스레드가 실행될 때 사용
- Heap과 Method 영역에 저장된 객체는 어디서든지 접근이 가능하지만, Stack 메모리는 다른 스레드가 접근 불가
- 객체 생성 시 항상 Heap 영역에 저장되며, 스택 메모리는 Heap 영역에 있는 객체를 참조
- Stack 메모리는 원시(primitive) 타입의 지역 변수와 힙 공간에 있는 객체 참조 변수만 갖고 있다.
- Stack 메모리의 생명주기는 매우 짧고 Heap 메모리는 어플리케이션의 시작~끝까지 살아남는다.
- Java 코드를 실행할 때 따로
-Xms
와-Xmx
옵션 사용을 통해 Heap 메모리의 초기 사이즈와 최대 사이즈를 조정 가능하다. - Stack 메모리가 가득차면 java.lang.StackOverFlowError를 발생
- Heap 메모리가 가득차면 java.lang.OutOfMemoryError : Java Heap Space에러를 발생
- Stack 메모리 사이즈는 Heap 메모리와 비교했을 때 매우 적다. 하지만 Stack 메모리는 LIFO를 통해 간단하게 메모리를 할당하기에 Heap 메모리보다 빠르다.
This post is licensed under CC BY 4.0 by the author.