JVM의 메모리 구조로 알아보는 call by value & call by reference

김태훈·2023년 11월 30일
0
post-thumbnail

응용 프로그램이 실행되면, JVM은 프로그래밍을 수행하는데 필요한 메모리를 할당받는다. 그 이후 메모리에 영역을 나누어 관리한다.
메모리 구조는 다음과 같다.

  1. Method Area
    프로그램 실행중 어떤 클래스가 사용되면, 컴파일 이후 결과값인 class 파일(클래스 정보)을 이곳에 저장한다. 클래스 변수도 이 영역에 함께 생성된다.
  2. Heap
    인스턴스가 생성되는 공간이다. 프로그램 실행중에 생성되는 인스턴스는 모두 이곳에 저장된다. 인스턴스 변수도 이 영역에서 생성된다. 간단하게 말하면, 클래스 정보는 Method Area에 로드되고, 이 정보를 기반으로 인스턴스를 생성할 때에는 Heap 영역에 메모리가 할당되고 객체의 데이터가 저장된다.
  3. Call Stack
    메서드 파트에 등장한 이유다.
    메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출 스택에 해당 메서드를 위한 메모리가 할당되고, 여기에 지역변수를 저장한다. 그리고 메서드가 반환되면, 해당 공간은 비워진다.
    중요한건 'Stack' 구조다. LIFO 아시죠? 제일 나중에 호출된 메서드가 가장 처음 비워진다는 뜻이다.
    (스택 오버 플로우 많이 보셨죠?)

(2) 기본형 매개변수와 참조형 매개변수

기본형 매개변수를 사용하여 메서드에 넘기면, 메서드는 해당 매개변수를 수정해도, 원래 값에는 변화가 없다.
하지만 참조형 매개변수를 사용하면, 변화한다. 이유는 참조형 매개변수는 '주소'값을 넘기기 때문이다.
이것이 call by reference 이다.

다음은 call by value 설명을 위한 예제 코드이다.
출처 : 자바의 정석(남궁성)

class Data {
    int x;
}

class PrimitiveParamEx {
    public static void main(String[] args) {

        Data d = new Data();
        d.x = 10;
        System.out.println("main() : x = " + d.x);

        change(d.x);
        System.out.println("After change(d.x)");
        System.out.println("main() : x = " + d.x);

    }

    static void change(int x) {
        x = 1000;
        System.out.println("change() : x = " + x);
    }
}

간단히 소개된 책 내용을 좀더 상세히 이해하며 그림을 작성해보았다.
JVM의 모든 내용이 다 드러나게 말이다.

  1. new Data()
    main 메서드에서 Data 인스턴스를 생성한다. 생성하기 위해서 먼저, JVM이 Data 클래스 정보를 받아오기 위해 Method Area에 해당 정보를 먼저 불러온다.
    그리고, 해당 정보를 이용하여 Heap영역에 인스턴스의 정보를 할당한다.
  2. Data d = new Data()
    할당된 인스턴스 정보(Heap 에 위치)를 main메서드의 참조 변수이자 로컬 변수인 d가 가리키게 한다. 동시에 main메서드는 호출스택 내에 Data 클래스타입의 로컬 변수 d를 가지게 된다.
  3. d.x = 10
    그러고 인스턴스의 x정보를 바꾸었다. 그러면 로컬변수 d가 가리키고 있는 Heap 영역의 인스턴스 정보를 바꾸게 된다.
  4. change 메서드를 실행한다.
    main 위에 호출스택으로 쌓이게 되며, d.x의 값을 그대로 복사한다.
    하지만 문제는, d.x는 참조변수가 아니라, 기본형 매개 변수이다. 즉 주소값이 아니라 값 자체이다. 그렇기 때문에, 변수값만 복사해와서 메서드안에서 1000으로 변경한다.
  5. change 메서드가 종료되더라도, main 메서드 안에서 참조변수 d가 가리키고 있는 x값은 변하지 않는다.

다음은 call by reference 이다.

class Data {
    int x;
}

class ReferenceParamEx {
    public static void main(String[] ar![](https://velog.velcdn.com/images/goat_hoon/post/aff517b9-47b8-48f6-87aa-192185c3a7c7/image.png)
gs) {

        Data d = new Data();
        d.x = 10;
        System.out.println("main() : x = " + d.x);

        change(d);
        System.out.println("After change(d)");
        System.out.println("main() : x = " + d.x);

    }

    static void change(Data d) {
        d.x = 1000;
        System.out.println("change() : x = " + d.x);
    }
}

아까랑 달라진점은, change 함수내에서 직접 Heap영역에 존재하는 인스턴스의 주소값을 복사한 것 밖에 없다.
이렇게 주소값을 복사하면 데이터값을 저장하고 있는 Heap 영역에 접근할 수 있으므로, 값을 메서드를 통해 변경할 수 있다.

https://velog.io/@goat_hoon/자바의-정석-6.-객체지향언어
제가 쓴 글에서 따로 발췌해온 포스트입니다.

profile
기록하고, 공유합시다

0개의 댓글