본문 바로가기

공부 정리

Java Heap vs Stack (Memory Allocation)

반응형

개요


자바에서 메모리 관리를 하는 곳은 크게 Heap 영역과 Stack 영역이 있습니다. 해당 영역에는 어떤 데이터들이 저장되고, 어떤 방식으로 동작하는지 알아보도록 하겠습니다.

 

 

 

원시변수와 참조변수


스택과 힙을 이해하기 이전에 사전 지식으로 원시변수와 참조변수를 알아보겠습니다.

 

Data Types from https://www.artima.com/insidejvm/ed2/jvm3.html

 

 

원시변수는 자바에서 사용하는 원시 자료 구조로 float, double, byte, short, int, long, char 등이 있습니다.

참조변수는 객체를 참조하고 있는 변수들입니다.

 

 

Java Stack Memory


자바 스택 메모리에 할당되는 것은 다음과 같습니다.

 

  • static 메모리
  • 쓰레드 실행
  • 메서드에서만 사용되는 원시변수(Primitive Types)
  • 객체들의 참조변수(Reference Types)

 

 

Stack 메모리는 항상 LIFO(Last-In-First-Out)의 순서로 작동합니다. 일반적으로 Stack의 메모리 크기가 Heap 메모리 크기보다 훨씬 더 적습니다. 새로운 메서드를 호출하면, 해당 메서드의 원시변수가 객체 참조를 포함하는 블럭이 스택의 맨 위에 생성됩니다.

 

Stack에 여러개의 메소드가 있을 경우 각 Thread는 자신의 Scope에 있는 메모리만 접근이 가능하고 다른 Thread의 thread의 메모리에 접근하지 않습니다. 현재 실행중인 변수들에 대해서만 접근이 가능하며, 현재 Scope가 아니라면 다른 원시변수들에 접근 할 수 없습니다. 하나의 메소드가 끝나고 반환되면, 맨 위에 있는 stack이 제거되고 Scope가 바뀌게 됩니다. 

 

각 쓰레드가 자신의 스택 블럭 범위 내에서만 작동하기 때문에 메모리는 쓰레드 안전합니다.

 

 

Java Heap Space


  자바 힙 공간은 자바 객체의 동적 할당과 JRE class들에 메모리를 할당하기 위해 자바 runtime에 사용됩니다. 객체가 생성되면 항상 heap 영역에 생성됩니다. 이 객체들의 참조는 스택 메모리에 저장됩니다.

 

heap 영역에 만들어진 객체들은 모두 전역변수를 가지고 있는데 어플리케이션의 어느곳에서도 참조가 가능합니다.

 

Stack은 메서드 작동이 모두 끝나면, 자동으로 메모리가 초기화되지만, Heap에 있는 메모리는 자동 초기화가 되지 않으며, Garbage Collector가 이 역할을 맡습니다. Gabage Collection은 heap 영역에 존재하여 어떠한 참조도 하지 않는 객체가 차지하는 메모리를 초기화(free)해줍니다.

 

 

Stack vs Heap


Stack과 Heap을 비교해보겠습니다.

 

Stack Heap
실행한 하나의 쓰레드에 의해서만 사용되는 메모리 어플리케이션의 어느 부분에서도 사용 가능한 메모리
지역 원시함수, 참조변수를 포함 객체가 생성 시 객체 저장
다른 쓰레드에 의해 접근 금지 어느 위치에서든지 heap의 객체 접근 가능
LIFO 방식으로 동작 전역적으로 사용되므로 복잡한 과정
-Xss(stack 메모리 사이즈 정의) -Xms(heap 시작 사이즈), -Xmx(heap 최대 사이즈)
java.lang.StackOverFlowError(초과 시) java.lang.OutOfMemoryError : Java Heap Space(초과 시)
작은 메모리 사이즈 상대적으로 큰 메모리 사이즈

 

 

실행 과정 1


객체의 생성 및 메서드 실행의 과정을 라인별로 알아보겠습니다.

 

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
}

 

 

https://www.journaldev.com/4098/java-heap-space-vs-stack-memory

 

  • line1

프로그램이 실행되면 Runtime 클래스들이 Heap에 로드됩니다. main() 메소드가 실행되면, Java Runtime은 main() 메소드 쓰레드에 사용할 stack 메모리를 생성합니다.

 

  • line2

int i=1로 main() 메소드 내부에 지역변수 int i가 생성되는데 stack 메모리에 쌓입니다.

 

  • line3, line4

Object object = new Object() 에서 new Ojbect()로 객체가 생성되는데 이는 heap 메모리에 생성됩니다. 이를 참조하는 Object object 변수가 stack 메모리에 저장됩니다. line4에서 Memory 객체에서도 같은 과정을 반복합니다.

 

  • line5, line6

mem.foo(obj) 로 메소드를 호출하면, stack 메모리에 foo()메소드를 위한 새로운 블럭이 생깁니다. foo() 메소드의 매개변수는 Object parameter가 있는데 처음에 이 매개변수를 stack 메모리의 foo() 블럭 안에 쌓습니다.

 

  • line7

String str = param.toString() 으로 생성된 String str은 stack에 저장되고, param.toString() 은 heap의 String pool 영역에 만들어집니다. 

 

  • line8

foo() 메서드가 끝나면, foo() 메서드에 할당되었던 stack 메모리가 초기화(free) 됩니다.

 

  • line9

main() 메서드가 끝나면 main()에 할당 된 stack 메모리가 초기화되고 사라집니다. 또한 프로그램이 끝나기 때문에 Java Runtime은 모든 메모리를 초기화시키고 프로그램의 실행을 종료합니다.

 

 

 

실행과정 2


이번에는 PersonBuilder 클래스로 Person 클래스 객체를 생성하는 방법을 알아보겠습니다.

 

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class PersonBuilder {
    private static Person buildPerson(int id, String name) {
        return new Person(id, name);
    }

    public static void main(String[] args) {
        int id = 23;
        String name = "John";
        Person person = null;
        person = buildPerson(id, name);
    }
}

 

 

 

Call Stack을 보면 main -> buildPerson -> Person 순서대로 적재됩니다. 맨 처음 main 메서드가 실행되고 buildPerson이 호출되며, 그 안에서 다시 Person 객체를 만듭니다. 크게 3가지의 중요 작업 흐름을 확인해보겠습니다.

 

  • main 메서드

main 메서드가 호출되면 메서드 안에 원시변수들이 stack에 저장됩니다. String name은 스트링 풀에 저장이 되며, Person person은 stack 에 로드되고, 힙 메모리에 있는 객체를 참조합니다.(현재는 null입니다.)

 

  • PesronBuilder

PesronBuilder 클래스의 buildPerson 메서드 내에 원시변수 id와 name이 stack에 저장됩니다. 처음에 main 메서드에서 Person person이 null로 할당되었지만, new Person()으로 새로운 객체를 생성하며, 힙 메모리에 있는 객체를 참조하고 있습니다.

 

  • Person

Person 생성자는 null에서 buildPerson() 메서드로 새로운 객체가 할당이 됩니다. 지역변수인 id와 name을 stack에저장합니다. 자신의 객체를 생성했기 때문에 stack에 Person 참조는 자신인 this입니다. 

 

 

3개의 메서드 실행은 FILO 방식으로, Person -> buildPerson -> main 순서대로 사용 및 메모리 초기화가 됩니다.

 

 

 

참고

 

https://www.baeldung.com/java-stack-heap

http://www.journaldev.com/4098/java-heap-memory-vs-stack-memory-difference

반응형

'공부 정리' 카테고리의 다른 글

다형성 / Up-casting & Down-casting  (0) 2020.07.20
Wrapper class / Integer cache pool  (0) 2020.07.17
Override vs Overload  (0) 2020.07.17
equals vs == (string pool)  (0) 2020.07.14
JDK, JRE  (0) 2020.07.14