자바 메모리 관리 Stack 과 Heap(2)

새로운 값이 heap에 생길 뿐 값이 바뀌지 않는다.

public class Main {
    public static void main(String[] args) {
        Integer a = 10;
        System.out.println("Before: " + a);
        changeInteger(a);
        System.out.println("After: " + a);
    }

    public static void changeInteger(Integer param) {
        param += 10;
    }
}

레퍼런스만 바뀌는 것이므로 a의 값은 20이 될 것이라고 기대한다.
지금까지 정리한 내용을 기초해서 실행순서대로 살펴보면,

  • Integer는 Object 타입이므로, 첫 구문인 Integer a = 10;에서 10은 heap영역에 할당되고, 10을 가리키는 레퍼런스 변수 a가 스택에 할당된다.
  • 함수에 인자를 넘겨줄 때에 파라미터는 copied value를 넘겨받는다.
  • 그러므로 changeInteger(a)에 의해, param이라는 레퍼런스 변수가 스택에 할당되고, 이 param은 main()에서 a를 가리키던 곳을 똑같이 가리키고 있다.
  • main()에서 레퍼런스하던 a와 같은 곳을 param이 가리키고 있으므로 param에 10을 더하면, changeInteger()가 종료되고 a의 값을 출력했을 때 바뀐 값이 출력될 것이다.

하지만 값이 안바뀐다... 뭐지?

public class Main {
    public static void main(String[] args) {
        String s = "hello";
        changeString(s);
        System.out.println(s);
    }
    public static void changeString(String param) {
        param += " world";
    }
}

changeString() 내부동작만 살펴보면,

  • main()메소드의 s변수가 레퍼런스하는 "hello" Object를 param에 복사하면서 changeString()가 시작된다.
  • param += "world";를 실행하는 것은 heap에 "hello world"라는 String Object가 새롭게 할당되는 작업이다.
  • 기존에 "hello" 오브젝트를 레퍼런스하고 있던 param으로 새롭게 생성된 String Object인 "hello world"를 레퍼런스 하도록 만드는 것이다.
    -changeString()함수가 종료되면, 새롭게 생성된 "hello world" Object를 레퍼런스 하는 param이라는 변수는 stack에서 pop되므로 어떤 것도 레퍼런스 하지 않는 상태가 된다.
  • 이런 경우 "hello world" Object는 garbage로 분류된다.

그러므로 changeString()를 수행하고 돌아가도 기존에 "hello"를 레퍼런스하고 있던 s변수의 값은 그대로이다. Immutable Object는 불변객체로써, 값이 변하지 않는다. 변경하는 연산이 수행되면 변경하는 것처럼 보이더라도 실제 메모리에는 새로운 객체가 할당되는 것이다.

자바에서 Wrapper Class에 해당하는 Integer, Character, Byte, Boolean, Long, Double, Float, Short 클래스 모두 Immutable 하다. 그래서 heap 있는 같은 Object를 레퍼런스 하고 있는 경우라도, 새로운 연산이 적용되는 순간 새로운 Object가 heap에 새롭게 할당된다.

Garbage Collection 살짝 겉핥아보기

이제 간단한 코드를 살펴보면서 garbage collection이 뭔지 살짝만 알아보자.

public class Main {
    public static void main(String[] args) {
        String url = "https://";
        url += "yaboong.github.io";
        System.out.println(url);
    }
}

위 코드에서

String url = "https://";

구문이 실행된 뒤, Stack과 heap은 아래와 같다.

다음 구문인

url += "yaboong.github.io";

문자열 더하기 연산이 수행되는 과정에서, (String은 불변객체이므로) 기존에 있던 "https://" String에 "yaboong.github.io"를 덧붙이는 것이 아니라 문자열에 대한 더하기 연산이 수행된 결과가 새롭게 heap 영역에 할당된다. 그 결과를 그림으로 표현하면 아래와 같다.

stack에는 새로운 변수가 할당되지 않는다. 문자열 더하기 연산의 결과인 "http:s//yaboong.github.io"가 새롭게 heap 영역에 저장되고 기존에 "https://"를 레퍼런스 하고 있던 url 변수는 새롭게 생성된 문자열을 레퍼런스하게 된다.

기존의 https:// 라는 문자열을 레퍼런스하고 있는 변수는 아무것도 없으므로 Unreachable Object가 된다.

JVM의 Garbage Collector는 Unreachable Object를 우선적으로 메모리에서 제거하여 메모리 공간을 확보한다. Unreachable Object란 Stack에서 도달할 수 없는 Heap 영역의 객체를 말하는데, 지금의 예제에서 "https://"문자열과 같은 경우가 되겠다.

Garbase Collection 과정은 Mark and Sweep이라고도한다. JVM의 Garbage Collector가 Stack의 모든 변수를 스캔하면서 각각 어떤 Object를 레퍼런스하고 있는지 찾는 과정이 Mark다. Reachable 오브젝트가 레퍼런스하고 있는 Object 또한 marking 한다. 첫번째 단계인 marking 작업을 위해 모든 Thread는 중단되는데 이를 stop the world라고 부르기도 한다. (System.gc()를 생각없이 호출하면 안되는 이유이기도 하다)

그리고 나서 mark 되어있지 않은 모든 Object들을 Heap에서 제거하는 과정이 Sweep이다.

Garbage Collection이라고 하면 garbage 들을 수집할 것 같지만 실제로는 garbage를 수집하여 제거하는 것이 아니라, garbage가 아닌 것을 따로 mark하고 그 외의 것은 모두 지우는 것이다. 만약 heap에 garbage만 가득하다면 제거 과정은 즉각적으로 이루어진다.

Garbage Collection이 일어난 후의 메모리 상태는 아래와 같을 것이다.

비슷한 예제를 하나 더 살펴보자.

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

public class Main {
    public static void main(String[] args) {
        List<String> listArgument = new ArrayList<>();
        listArgument.add("yaboong");
        listArgument.add("github");

        print(listArgument);
        
        listArgument = new ArrayList<>();
    }

    private static void print(List<String> listParam) {
        listParam.add("io");
        System.out.println(listParam);
    }
}

위 코드에서는 listArgument라는 변수에 두번의 할당작업이 일어난다. 위와 같이 실행한 경우 stack과 heap영역은 아래와 같이 될 것이다.

기존에 사용했던 listArgument 참조변수는 새롭게 생성한 빈 List를 레퍼런스한다. 세개의 String Object는 List 내부의 인덱스에 의해 레퍼런스 되고 있지만 Unreachable한 영역에 있다. 기존에 listArgument가 참조했던 "yaboong", "github", "io"를 가진 ArrayList를 참조하고 있는 변수는 어느 stack에서도 찾아볼 수 없다.

앞서 본 경우와 비슷하게 이런 경우에도 기존의 List Object와, List Object가 힙 내부에서 레퍼런스하고 있는 String object들도 모두 garbage로 분류된다.

Garbage Collection이 일어난 후의 stack과 heap 영역은 아래와 같을 것이다.

원글: https://yaboong.github.io/java/2018/05/26/java-memory-management/

profile
클린코드와 UX를 생각하는 비즈니스 드리븐 소프트웨어 엔지니어입니다.

0개의 댓글