자바의 Heap과 Stack

2020-03-29

자바의 메모리 구조

자바 힙 공간(Java Heap Space)

자바의 힙 공간은 객체와 JRE 클래스들에게 메모리를 할당할 때 사용됩니다. 우리가 객체를 만들 때 항상 힙 공간에 만들어 집니다.

여기에서 가비지컬렉션은 힙 메모리에 더 이상 참조하지 않는 객체들을 정리를 합니다. 힙에 만들어진 객체는 어디서든지 접근할 수 있고, 애플리케이션 어디에서나 참조될 수 있습니다.

자바 스택 메모리(Java Stack Memory)

자바 스택 메모리는 스레드를 실행하기 위해 사용됩니다. 짧은 시간 존재하는 메소드나 다른 메소드가 참조되는 힙안에서 다른 객체를 참조를 포함합니다.

스택 메모리는 항상 LIFO(Last-In-First-Out) 순서로 참조됩니다. 언제든지 메소드가 호출되면 그 메소드안에 있는 primitive 타입의 지역 변수객체의 참조를 갖기 위한 새로운 블록단위로 스택 메모리에 생성됩니다.

메소드 실행이 끝이나면, 그 블록은 사라집니다. 스택 메모리의 크기는 힙 메모리에 비해 매우 적습니다.

힙 과 스택 메모리

힙과 스택을 간단한 예제로 알아보겠습니다.

public class Memory {

	public static void main(String[] args) { // Line 1
		int i=1; // Line 2
		Object obj = new Object(); // Line 3
		Memory mem = new Memory(); // Line 4
		mem.foo(obj); // Line 5
	} // Line 9

	private void foo(Object param) { // Line 6
		String str = param.toString(); //// Line 7
		System.out.println(str);
	} // Line 8

}

아래 그림은 위에 코드를 그림으로 힙과 스택을 나타냈습니다.

프로그램을 실행시키자마자, 사용할 클래스들을 힙 공간에 올립니다. 실행 시킨후 첫번 째 줄인 main() 메소드를 만나면 자바 런타임은 main() 메소드 스레드가 사용할 스택 메모리를 만듭니다.

두번 째 줄에는 primitive 타입인 지역 변수를 만들었습니다. primitive 타입이기 때문에 main() 메소드의 스택메모리 안에 저장됩니다.

세번 째 줄에서는 Object 클래스를 만들었기 때문에, 힙 메모리안에 만들어지며, 스택 메모리에서는 Object의 참조를 갖습니다. 이 과정은 4번째 줄인 Memory 클래스를 만들 때도 비슷하게 이루어집니다.

이제 다섯번 째 줄인 foo() 메소드를 실행하면 스택메모리 맨 위에 foo() 메소드가 사용할 블록이 생성 됩니다. 자바는 객체의 주소가 아닌 값을 넘기기 때문에 6번 째 줄인 param 값은 foo() 스택 블록안에서 Object의 주소의 값을 받아 새롭게 참조를 합니다.

7번 째줄은 String 객체를 만드는데, 힙 공간안에 있는 String Pool에서 만들어지며 foo() 스택 공간에서 String Pool을 참조하는 str 변수가 만들어집니다. (자바는 String 객체를 만들 때 String Pool을 이용합니다.)

8번 째줄에서 foo() 메소드가 끝납니다. 이 때 스택안에 있는 foo()가 차지하고 있는 메모리 블록이 사라집니다.

9번 째줄에서도 main() 메소드가 끝나게되면 스택에 할당되어 있던 main() 스택 메모리도 사라집니다. 또한 프로그램이 이 9번 째 줄에서 끝나게되며, 자바 런타임이 모든 메모리를 비우고 프로그램을 종료합니다.

힙 공간과 스택 메모리의 차이점

  1. 힙 메모리는 애플리케이션의 모든 부분에서 사용되며, 반면에 스택 메모리는 하나의 스레드가 실행될 때 사용됩니다. (추가로 ThreadLocal도 공부해보시는걸 추천합니다!)
  2. 언제든지 객체가 생성되면, 항상 힙 공간에 저장되며, 스택 메모리는 힙 공간에 있는 객체를 참조만 합니다. 스택 메모리는 primitive 타입의 지역변수와 힙 공간에 있는 객체 참조 변수만 갖고 있습니다.
  3. 힙 공간에 저장된 객체는 어디서든지 접근이 가능하지만, 스택 메모리는 다른 스레드가 접근할 수 없습니다.
  4. 스택안에서 메모리 관리는 LIFO 방법으로 관리되며, 반면에 힙 공간은 글로벌하게 사용되기때문에 좀 더 복잡합니다. 힙 메모리를 좀 더 구분하자면, 객체가 생성된지 얼마 안됐다면 Young 영역에 있고, 생성된지 오래 됐다면 Old 영역에 포함됩니다. 좀 더 자세한 내용은 추후에 GC(Garbage Collection)에서 포스팅 하겠습니다.
  5. 스택메모리의 생명주기는 매우 짧으며, 힙 메모리는 애플리케이션의 시작부터 끝까지 살아남습니다.
  6. JVM에 있는 -Xms과 -Xmx 옵션을 사용하면 힙 메모리의 초기 사이즈와 최대 사이즈를 조절할 수 있습니다.
  7. 스택 메모리가 가득차면 자바에서는 java.lang.StackOverFlowError를 발생시킵니다. 힙 메모리가 가득차면 java.lang.OutOfMemoryError : Java Heap Space 에러를 발생시킵니다.
  8. 스택 메모리 사이즈는 힙 메모리와 비교했을 때 매우 적습니다. 스택 메모리는 간단한 메모리 할당 방법(LIFO)를 사용하므로 힙 메모리보다 빠릅니다.