Welcome to Wesley & Harry's Traveling & Programming

IT Program/Java Basic

자바 메모리 관리 스택(stack) & 힙(heap) 영역 설명, 예시

Wesley & Harry 2022. 3. 2. 19:25
반응형

스택 영역

 

함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역

스택 영역의 크기는 컴파일 시에 결정

스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸

이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)

스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당

푸시(push) 동작으로 데이터를 저장하고, 팝(pop) 동작으로 데이터를 인출

 

후입선출(LIFO, Last-In First-Out) 방식

박스안에 물건을 차곡차곡 넣는것이라 생각하면 편리하다.

제일 먼저 넣은 것을 빼내려면 마지막에 넣은것부터 빼내야 하는 구조이다.

 

스택에 대해 코드를 통해 알아보도록 하겠다.

public class stackEx {
    public static void main(String[] args) {
        int wesley = 4;
        int resultwesley = wesleyFunction(wesley);
        System.out.println(resultwesley);
    }

    private static int wesleyFunction(int harry){
        int tmp = harry * 6;
        int result = tmp + 2;
        return result;
    }

}

위의 코드에 대한 설명을 먼저 해보겠다.

우선 int wesley에 4를 저장한후

wesleyFunction함수를 호출한다. wesleyFunction에 전달되는 값은 4가 되며 int harry 부분에 4가 들어가게 된다.

wesleyFunction함수 내에서 int tmp = harry * 6이 실행되어 tmp의 값은 24가 된다.

int result = tmp + 2;가 실행되며 result는 26이 되고

return result;를 통해 result 값을 

int resultwesley에 저장한다.

그다음 resultwesley를 출력한다.

우선 이러한 형태는 처음보는사람이 있을 수 있다. 나중 게시물에 설명을 할테니 우선 따라하고 이해하도록 해보자.

우린 스택에 대해 이해하기로 했다. 각각 라인별로 저장되는 형태를 살펴보자.

int wesley = 4;

우리는 wesley의 값을 4라고 저장했다.

스택메모리는 쓰레드 하나당 하나씩 할당한다.

stack에 저장된 형태를 살펴보자.

 

다음 구문을 살펴보자.

int resultwesley = wesleyFunction(wesley);

wesleyFunction()함수가 호출이 되면서 wesley 변수를 넘겨줘 이동한다.

인자로 넘겨받은 값은 파라미터인 harry에 복사되어 전달된다.

harry또한 stack에 할당된 공간에 값이 할당된다.

흐름이 wesleyFunction으로 이동하게 되면서 wesley를 사용할 수 없게된다.

스택의 상태이다.

다음 구문이다.

int tmp = harry * 6;
int result = tmp + 2;

같은 방식으로 똑같이 stack에 저장된다.

이후 "}" 닫는 괄호가 실행되면 wesleyFunction()의 함수가 종료된다.

종료됨과 동시에 모든 지역변수들은 스택에서 pop이 되어 사라진다.

스택은 아래와 같아진다.

마지막으로 main 함수가 종료되는 순간 스택에 있는 모든 데이터는 pop되어 날아가게된다.

 


 

다음은 heap 영역이다.

Heap 영역에는 주로 긴 생명주기를 가지는 데이터들이 저장된다. (대부분의 오브젝트는 크기가 크고, 서로 다른 코드블럭에서 공유되는 경우가 많다)

애플리케이션의 모든 메모리 중 stack 에 있는 데이터를 제외한 부분이라고 보면 된다.

모든 Object 타입(Integer, String, ArrayList, ...)은 heap 영역에 생성된다.

몇개의 스레드가 존재하든 상관없이 단 하나의 heap 영역만 존재한다.

Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수가 stack 에 올라가게 된다.

 

코드예제를 보겠다.

public class Ex {
	public static void main(String[] args) {
    	int wesley = 20;
    	String harry = "tistory";
    }
}

int wesley = 20; 은 위에서 봄과 같이 stack에 저장된다.

그리고 String harry = "tistory"; 구문은 아래와 같은 형태로 되어있다.

String 은 heap 영역에 할당되고 stack 에 harry 라는 이름으로 생성된 변수는 heap 에 있는 “tistory” 라는 스트링을 참조하게 된다.

 

아래 또다른 예시를 보도록 한다.

List는 배우지 않았지만 추후 게시글을 통해 알아보도록 하겠다.

package wesley.java.practice.basic;

import java.util.ArrayList;
import java.util.List;

public class stackEx2 {
    public static void main(String[] args) {
        List<String> exampleList = new ArrayList<>();
        exampleList.add("wesley");
        exampleList.add("harry");

        printMyList(exampleList);
    }
    private static void printMyList(List<String> listParam) {
        String value = listParam.get(0);
        listParam.add("tistory");
        System.out.println(value);
    }
}

코드의 흐름부터 간단하게 살펴보도록 하겠다.

우선 코드는 main 메소드로 들어가 각 구문을 실행 시킨후 printMyList(exampleList)까지 실행을 시킨다.

그럼 exampleList의 값이 printMyList 함수의 listParam에 저장이 된다. 그 후 각 구문이 완료되면 다시 main 메소드의

printMyList(exampleList); 다음 구문으로 진행하는 방식이다.

 

한줄씩 뜯어가며 stack과 heap에 대해 이해해보도록 하자.

 

List<String> exampleList = new ArrayList<>();

생성하려는 오브젝트를 저장할 수 있는 충분한 공간이 heap 에 있는지 먼저 찾은 다음, 빈 List 를 참조하는 exampleList라는 로컬변수를 스택에 할당한다. new의 역할이기도 하다.

해당 한줄의 코드를 그림으로 살펴보도록 하자. 앞선 String형태와 크게 다르지 않다.

다음 구문이다.

exampleList.add("wesley");

exampleList.add 의 뜻은 해당exampleList의 이름을 가지고 있는 List에 값을 넣는다는 뜻이다.

이 구문은 exampleList

.add(new String("wesley")); 와 같은 역할을 한다.

new 키워드에 의해 heap 영역에 충분한 공간이 있는지 확인한 후 “wesley” 이라는 문자열을 할당하게 된다.

이때 새롭게 생성된 문자열인 “wesley” 을 위한 변수는 stack 에 할당되지 않는다.

List 내부의 인덱스에 의해 하나씩 add() 된 데이터에 대한 레퍼런스 값을 갖게 된다.

해당 구문이 실행되면 어떤모습일까

다음 구문 또한 똑같은 방식으로 동작한다.

exampleList.add("harry");

 

다음 구문이다.

printMyList(exampleList);

함수의 호출이 이루어진다.

exampleList 라는 참조변수를 넘겨주게 된다. 함수호출시 원시타입의 경우와 같이 넘겨주는 인자가 가지고 있는 값이 그대로 파라미터에 복사된다.

printMyList(List<String> listParam) 메소드에서는 listParam 이라는 참조변수로 인자를 받게 되어있다. 따라서 printMyList() 함수호출에 따른 메모리의 변화는 아래와 같다.

 

listParam 이라는 참조변수가 새롭게 stack 에 할당되어 기존 List 를 참조하게 되는데, 기존에 인자인 exampleList 가지고 있던 값(List 에 대한 레퍼런스)을 그대로 listParam 이 가지게 되는 것이다. 그리고 printMyList() 함수 내부에서 exampleList는 scope 밖에 있게 되므로 접근할 수 없는 영역이 된다. 주황색은 함수내부에서 접근을 표현하였다.

 

다음 구문들을 살펴본다.

String value = listParam.get(0);
listParam.add("tistory");
System.out.println(value);

다음으로, printMyList() 함수 내부에서는 List 에 있는 데이터에 접근하여 값을 value 라는 변수에 저장한다. 이 때 printMyList() 함수의 scope 에서 stack 에 value 가 추가되고, 이 value 는 listParam 을 통해 List 의 0번째 요소에 접근하여 그 참조값을 가지게 된다. 그리고나서 또 데이터를 추가하고, 출력함으로 printMyList() 함수의 역할은 마무리 된다.

영역을 살펴보도록하자. 함수 종료 직전의 상태이다. 여전히 exampleList부분은 접근이 불가능한 상태이다. 이를 인지하자.

이제 함수가 닫는 중괄호 } 에 도달하여 종료되면 printMyList() 함수의 지역변수는 모두 stack 에서 pop 되어 사라진다. 이때, List 는 Object 타입이므로 지역변수가 모두 stack 에서 pop 되더라도 heap 영역에 그대로 존재한다. 즉, 함수호출시 레퍼런스 값을 복사하여 가지고 있던 listParam 과 함수내부의 지역변수인 value 만 스택에서 사라지고 나머지는 모두 그대로인 상태로 함수호출이 종료된다.

이로써 stack과 heap 영역에 대해 알아보았다.

자바 메모리 관리에 대해 가볍게 이해하도록 넘어가봅시다.

 

 

 

 

 

반응형