앤디 블로그
  • 모두
  • 개발 문화
  • 기술
  • 자바
  • 스프링
  • 인프라
  • 카프카
  • 데이터베이스
  • 컨퍼런스
책
짧은 글
이력서
  • 모두
  • 개발 문화
  • 기술
  • 자바
  • 스프링
  • 인프라
  • 카프카
  • 데이터베이스
  • 컨퍼런스
책
짧은 글
이력서
  • 자바 Pass By Value

    • 메모리 할당
      • 원시 타입 (Primitive Type)
      • 객체 타입 (Object Type)
    • Pass by Value 와 Pass by Reference
      • Pass by Value
      • Pass By Reference
    • Ref.

자바 Pass By Value

2025년 9월 23일

아래 코드에 대한 출력값(1,2,3,4)을 맞춰보자.

class Tom {
    String name;

    Tom (String name) {
        this.name = name;
    }
}

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

        String originStr = "originStr";
        change(originStr);
        System.out.println(originStr); //출력 1
      
        int[] originArr = {1, 2};
        change(originArr);
        System.out.println(originArr[0]); //출력 2

        List<Integer> originList = List.of(1, 2);
        change(originList);
        System.out.println(originList.get(0)); //출력 3

        Tom originTom = new Tom("origin Tom");
        change(originTom);
        System.out.println(originTom.name); //출력 4
    }

    public static String change(String str) {
        str = "changedStr";
        return str;
    }

    public static int[] change(int[] arr) {
        arr = new int[] {3, 4};
        return arr;
    }

    public static List<Integer> change(List<Integer> list) {
        list = List.of(5, 6);
        return list;
    }

    public static Tom change(Tom tom) {
        tom = new Tom("changed Tom");
        return tom;
    }
}

정답은 다음과 같다.

정답 origin Str / 1 / 1 / origin Tom

만약 (나와 같이) 정답을 맞추지 못했다면 포스트를 읽어보도록 하자.

메모리 할당

자바에서 선언하는 변수는 메모리에 저장된다. 런타임에 동적으로 변하는 메모리는 힙 메모리와 스택 메모리가 있다. Java 에는 원시 타입(Primitive Type) 과 객체 타입 (Object Type) 이 있는데 두 타입 간 메모리 할당 차이가 있다.

원시 타입 (Primitive Type)

다음과 같이 원시 타입 x,y 가 있다고 하자.

public void test() {
    // Primitive Value
    int x = 10;
    boolean y = false;
}

그러면 test 메서드가 호출될 때 해당 스레드의 스택 메모리에 다음과 같이 직접적으로 저장된다.

image-20250923155353003

객체 타입 (Object Type)

하지만 원시 타입을 제외한 모든 타입은 실제 값이 Heap 메모리에 저장되고, 스택 메모리에는 주소값만 저장된다.

public void test() {
    // Primitive Value
    int x = 10;
    boolean y = false;
  
  	String z = "hello";
}

그리고 그 주소값은 힙메모리의 실제 주소를 가리킨다.

image-20250923155638205

Pass by Value 와 Pass by Reference

Pass by Value (값에 의한 전달)과 Pass by Reference (참조에 의한 전달) 은 메서드의 파라미터로 복사된 데이터를 전달하느냐 실제 값을 전달하느냐의 차이이다.

Pass by Value

Pass By Value 는 복사된 데이터를 전달하여 구성함으로써, 값을 수정하여도 원본의 데이터에는 영향을 주지 않도록 하는 방식이다. 맨 처음 나왔던 문제 중 배열만 보겠다.

public class Main {
    public static void main(String[] args) {
      
        int[] originArr = {1, 2};
        change(originArr);
        System.out.println(originArr[0]); 
    }
  
    public static int[] change(int[] arr) {
        arr = new int[] {3, 4};
        return arr;
    }
}

먼저 change 를 호출하기 전 originArr 은 다음과 같이 보인다.

image-20250923160643918

그리고 change() 메서드가 호출되면 호출한 코드의 "돌아갈 위치"인 Return Address 가 쌓이게 되고 그 위에 매개변수 등의 값이 쌓인다.

image-20250923160933872

그리고 arr = new int[] {3, 4}; 을 호출하게 되면 "새로운 객체를 생성"한 후 arr 위치에 집어넣게 된다. 여전히 originArr 은 1, 2 이다. 만약 여기서 arr 을 수정한다고 해도 (arr[0] = 5와 같이) 0x002 주소의 배열값만 바뀌는 것이지 0x001 배열 값은 아무런 영향을 받지 않는다.

image-20250923161031064

그리고 change() 메서드가 끝나면 arr 지역 변수만 스택 메모리에서 pop 되어 사라지게 된다. (초록색 부분이 사라짐)

image-20250923161302509

따라서 arr 을 재할당한다고 하더라도 orginArr 의 실제 값에는 아무런 변화가 없다.

자바는 모든 데이터를 Pass by Value 방식으로 전달한다. 그렇기 때문에 자바에서 어떤 객체가 파라미터로 전달되었을 때, 필드값에 접근하여 해당 값을 수정하는 것은 가능하지만 그 객체 자체는 변경 불가능하다.

위 예시처럼 change() 메서드가 종료되면 복제본 (0x002)은 소멸되고, arr 객체 자체에 변경사항이 있었다면 해당 부분은 반영되지 않는다.

Pass By Reference

Pass By Reference 는 주소 값을 전달하여 실제 값에 대한 Alias를 구성함으로써, 값을 수정하면 원본의 데이터가 수정되도록 하는 방식이다. C++에서는 해당 방식을 사용할 수 있다.

#include <iostream>
using namespace std;

void change(int*& arr) {
    // 새로운 배열을 할당해서 arr 자체를 바꿈
    arr = new int[2]{3, 4};
}

int main() {
    int* originArr = new int[2]{1, 2};

    change(originArr);

    cout << originArr[0] << endl; // 출력: 3

    // 동적 메모리 해제
    delete[] originArr;

    return 0;
}

위 코드에서 originArr[0] 의 출력은 3 이 나오게 된다.

change() 함수의 int*& arr (포인터 참조 전달)은 스택 메모리에 따로 저장 되지 않는다. arr는 별칭(alias)이기 때문에, 스택에 "arr=0x002" 같은 슬롯이 생기지 않고, 컴파일러가 originArr를 직접 가리키도록 번역한다.

따라서 arr = new int[2]{3, 4} 가 실행 될 때 다음과 같이 동작된다.

image-20250923162538945

Ref.

  • https://medium.com/@samurai.stateless.coder/i-asked-50-java-developers-this-question-their-answers-will-shock-you-5964e9e5a2e3
  • https://mangkyu.tistory.com/106