프로그래밍 언어/java

[Java] final과 static: '불변'과 '공유'의 미학 (feat. JVM 메모리 구조)

하루이2222 2024. 7. 28. 19:30

Java의 final과 static: '불변'과 '공유'의 미학 (feat. JVM 메모리 구조)

finalstatic. 두 키워드는 문법의 가장 기초적인 부분이지만, 그 역할과 동작 원리를 깊이 이해하는 것은 견고한 코드를 작성하는 핵심입니다.

  • final은 **"한 번만 할당할 수 있다"**는 불변의 약속입니다.
  • static은 **"인스턴스가 아닌 클래스에 속한다"**는 공유의 개념입니다.

이 글에서는 finalstatic의 각기 다른 역할을 코드와 함께 살펴보고, 더 나아가 이들이 JVM 메모리의 어느 공간에 위치하며 동작하는지 심층적으로 알아보겠습니다.

1. final - "변경될 수 없는 마지막 값"

final 키워드는 "최종적인", "변경될 수 없는" 이라는 의미를 가집니다. 변수, 메소드, 클래스에 붙어 각기 다른 제약을 부여합니다.

final의 세 가지 얼굴

  1. final 필드: **상수(Constant)**가 되는 변수.

    • 선언 시 또는 생성자에서 단 한 번만 초기화할 수 있습니다.
    • 일단 값이 할당되면, 그 이후로는 절대 변경할 수 없습니다.
  2. final 메소드: 오버라이딩(Overriding)이 금지된 메소드.

    • 자식 클래스에서 이 메소드를 재정의할 수 없습니다.
    • 메소드의 핵심적인 구현이 변경되는 것을 막고 싶을 때 사용합니다.
  3. final 매개변수: 메소드 내에서 재할당이 금지된 매개변수.

    • 메소드 안으로 전달된 매개변수 변수에 다른 값을 다시 할당할 수 없습니다.

코드로 살펴보기

아래 코드는 final 키워드가 어떻게 동작하는지 보여줍니다.

public class FinalExam {
    // (1) final 필드: 선언과 동시에 초기화
    private final String message = "Final Message";

    public FinalExam() {
        // (2) 컴파일 오류! final 필드는 이미 초기화되었으므로 또 할당할 수 없다.
        // this.message = "Final Message~";
        // -> Error: Variable 'message' might already have been assigned
    }

    // (3) final 메소드: 이 메소드는 하위 클래스에서 오버라이딩 불가
    public final void showMessage() {
        System.out.println(message);
    }

    // (4) final 매개변수: 메소드 내에서 재할당 불가
    public void showMessage(final String message) {
        // 컴파일 오류! final 매개변수의 값을 변경할 수 없다.
        // message = "New Message!";
        // -> Error: Cannot assign a value to final variable 'message'
        System.out.println(message);
    }
}

2. static - "인스턴스를 초월한 단 하나의 공유지"

static 키워드는 "정적인"이라는 의미로, 객체(인스턴스)가 아닌 클래스에 소속되도록 만듭니다. static 멤버는 해당 클래스의 모든 인스턴스가 공유하는 단 하나의 공간입니다.

마치 한 학급의 모든 학생(인스턴스)이 함께 보는 **'공용 게시판'**과 같습니다. 개인 다이어리(인스턴스 변수)는 각자 가지고 있지만, 공용 게시판(static 변수)은 단 하나뿐이며 모두가 같은 내용을 봅니다.

코드로 살펴보기 - 학생 학번 자동 부여기

모든 학생 객체가 nextId라는 공유 변수를 사용하여 순차적으로 학번을 부여받는 예제입니다.

Student 클래스

public class Student {
    // static 필드: 모든 Student 인스턴스가 이 변수를 공유한다.
    private static int nextId = 1;

    // 인스턴스 필드: 각 Student 인스턴스마다 개별적으로 가진다.
    private int id;

    public void setId() {
        this.id = nextId; // 공유 변수 nextId를 개인 id에 할당
        nextId++;         // 공유 변수 값을 1 증가시킨다.
    }

    public int getId() {
        return id;
    }

    // 클래스 전체의 공유 자원에 접근
    public static int getNextId() {
        return nextId;
    }
}

실행 코드 (main 메소드)

public static void main(String[] args) {
    System.out.println("초기 nextId: " + Student.getNextId()); // 출력: 1

    Student minsu = new Student();
    minsu.setId(); // minsu.id = 1, nextId는 2가 됨
    System.out.println("민수 할당 후 nextId: " + Student.getNextId()); // 출력: 2

    Student heejin = new Student();
    heejin.setId(); // heejin.id = 2, nextId는 3이 됨
    System.out.println("희진 할당 후 nextId: " + Student.getNextId()); // 출력: 3

    System.out.println("민수 학번: " + minsu.getId()); // 출력: 1
    System.out.println("희진 학번: " + heejin.getId()); // 출력: 2
}

minsuheejin은 각자 다른 객체이지만, nextId라는 static 변수를 공유하기 때문에 순차적인 학번 부여가 가능합니다. static 멤버는 객체 생성(new) 없이 클래스이름.정적멤버 형태로 접근할 수 있습니다.


[심화] JVM 메모리에서 finalstatic은 어디에 살까?

이들의 동작 방식을 완벽히 이해하려면 변수들이 JVM 메모리의 어느 영역에 저장되는지 알아야 합니다. JVM 메모리는 크게 3가지 영역으로 나뉩니다.

  1. 메소드 영역 (Method Area)

    • 클래스의 '설계도'가 보관되는 곳입니다. 클래스 정보, 메소드 코드, 그리고 static 변수가 이곳에 저장됩니다.
    • 프로그램 시작 시 클래스가 로드될 때 생성되며, 모든 스레드가 공유합니다.
  2. 힙 영역 (Heap)

    • new 키워드로 생성된 모든 객체(인스턴스)가 거주하는 공간입니다.
    • 인스턴스 변수(멤버 변수)들이 객체 내부에 저장됩니다. final이 붙었든 아니든 인스턴스 변수는 모두 힙에 생성된 객체 안에 위치합니다.
  3. 스택 영역 (Stack)

    • 메소드가 호출될 때마다 해당 메소드만을 위한 '작업 공간'(스택 프레임)이 생성되는 곳입니다.
    • 메소드 내의 지역 변수, 매개변수가 이곳에 저장됩니다. 객체를 직접 저장하는 것이 아니라, 힙에 있는 객체를 가리키는 참조 주소를 저장합니다.
    • 메소드 실행이 끝나면 해당 스택 프레임은 즉시 사라집니다.

예제 코드를 바탕으로 한 메모리 배치

변수 종류 예시 코드 저장되는 메모리 영역 설명
static 필드 private static int nextId Method Area 클래스 자체에 속하며, 모든 인스턴스가 공유하는 단 하나의 변수.
인스턴스 필드 private int id Heap new Student()로 생성된 각 객체 내부에 개별적으로 존재.
final 인스턴스 필드 private final String message Heap new FinalExam() 객체 내부에 존재. final 여부와 상관없이 인스턴스 변수는 힙에 위치.
지역 변수 (참조형) Student minsu = ... Stack main 메소드의 지역 변수 minsu는 스택에 저장되며, 힙에 있는 실제 객체를 가리키는 주소값을 가짐.
final 매개변수 final String message Stack showMessage 메소드의 매개변수 message는 메소드 호출 시 스택에 생성됨.

결론

finalstatic은 Java의 근간을 이루는 중요한 키워드입니다.

  • final: 불변성을 부여하여 값의 변경을 막고 예측 가능하며 안전한 코드를 만듭니다.
  • static: 공유성을 부여하여 클래스 레벨의 데이터를 관리하고, 메모리를 효율적으로 사용하게 합니다.