인스턴스마다 따로 생기지 않고 클래스 전체가 하나를 공유하는 것.

클래스가 공유하는 변수

학생이 입학할 때마다 학번을 순서대로 매기려면 지금까지 몇 명이 들어왔는지 담을 곳이 필요하다. 인스턴스 변수로는 안 된다. 학생마다 따로 생기니 누구의 값이 기준인지 정할 수 없다. 클래스 전체가 공유하는 변수가 있어야 하고 그것이 static 변수다.

static int serialNum = 1000;   // 모든 인스턴스가 공유
int studentID;                 // 인스턴스마다 따로
 
StudentStatic() {
    serialNum++;
    studentID = serialNum;     // 1001, 1002, 1003 ...
}

static 변수, 정적 변수, 클래스 변수는 같은 말이다. 메모리 공간이 딱 한 번 할당되고 모든 인스턴스가 그 값을 본다.

메모리 영역과 생명주기

JVM은 메모리를 용도에 따라 나눠 관리한다. 클래스 정보와 클래스 변수는 메서드 영역에, 메서드가 쓰는 지역 변수와 매개변수는 호출 스택에, 생성된 인스턴스와 그 인스턴스 변수는 힙에 놓인다.

static이 인스턴스보다 먼저 존재하는 이유가 여기에 있다. 클래스가 로드되는 시점에 이미 메서드 영역에 자리를 잡기 때문이다. 자바 8부터 실제 저장 위치는 힙으로 옮겨갔지만(JVM과 바이트코드) 생명주기는 그대로다.

클래스 메서드와 static 블록

static이 붙은 메서드는 인스턴스 없이 클래스 이름으로 부른다. Math.max(a, b)가 그렇다.

static {
    // 클래스가 로드될 때 한 번만 실행된다
}

static 블록은 클래스 로딩 시점에 한 번 실행되는 초기화 블록이다. Bill Pugh 방식이 이 성질에 기댄다. JVM이 클래스 로딩에 이미 동기화를 걸어두므로 synchronized 없이도 스레드에 안전하다.

접근 제약과 수명

클래스 메서드 안에서는 인스턴스 변수를 쓸 수 없다. 클래스 메서드는 인스턴스보다 먼저 존재할 수 있고, 그 시점에 인스턴스 변수는 아직 없기 때문이다. 반대 방향은 된다. 인스턴스 메서드에서 클래스 변수를 읽는 것은 언제나 안전하다.

지역 변수는 메서드가 끝나면 사라지고 멤버 변수는 인스턴스가 살아 있는 동안 남지만, static 변수는 프로그램이 끝날 때까지 산다. 전역 변수처럼 편해 보이는 이유이자 남용하면 안 되는 이유다. 오래 살고 모두가 공유하므로 동기화 문제와 메모리 문제를 함께 불러온다.

관련

출처