회원가입

자바 메모리 구조 - static 변수

Beany 2024-09-17

static 변수


이번에는 새로운 키워드인 static 키워드에 대해 알아보자.

static 키워드는 주로 맴버 변수와 메서드에 사용된다.
먼저 맴버 변수에 static 키워드가 왜 필요한지 이해하기 위해 간단한 예제를 만들어보자.

특정 클래스를 통해서 생성된 객체의 수를 세는 단순한 프로그램이다.

 

 

인스턴스 내부 변수에 카운트 저장

먼저 생성할 인스턴스 내부에 카운트를 저장하겠다.

Data1

package memory;

public class Data1 {
    public String name;
    public int count;

    public Data1(String name) {
        this.name = name;
        this.count++;
    }
}

생성된 객체의 수를 세어야 한다. 따라서 객체가 생성될 때 마다 생성자를 통해 인스턴스의 맴버 변수인 count 값을 증가시킨다.
(참고로 예제를 단순하게 만들기 위해 필드에 public 을 사용했다.)

 

DataCountMain1

package memory;

public class DataCountMain1 {
    public static void main(String[] args) {
        Data1 data1 = new Data1("data1");
        System.out.println("data1.count: " + data1.count);

        Data1 data2 = new Data1("data2");
        System.out.println("data2.count: " + data2.count);

        Data1 data3 = new Data1("data3");
        System.out.println("data3.count: " + data3.count);
    }
}
data1.count: 1
data2.count: 1
data3.count: 1

이 프로그램은 당연히 기대한 대로 작동하지 않는다. 객체를 생성할 때 마다 Data1 인스턴스는 새로 만들어진다. 그리고 인스턴스에 포함된 count 변수도 새로 만들어지기 때문이다.

 

 

외부 인스턴스에 카운트 저장

이번에는 카운트 값을 저장하는 별도의 객체를 만들어보자

 

Counter

package memory;

public class Counter {
    public int count;
}

 

Data2

package memory;

public class Data2 {
    public String name;

    public Data2(String name, Counter counter) {
        this.name = name;
        counter.count++;
    }
}

 

DataCountMain2 

package memory;

public class DataCountMain2 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Data2 dataA = new Data2("A", counter);
        System.out.println("A.count: " + counter.count);

        Data2 dataB = new Data2("B", counter);
        System.out.println("B.count: " + counter.count);

        Data2 dataC = new Data2("C", counter);
        System.out.println("C.count: " + counter.count);
    }
}
A.count: 1
B.count: 2
C.count: 3

Counter 인스턴스를 공용으로 사용한 덕분에 객체를 생성할 때 마다 값을 정확하게 증가시킬 수 있다.

 

그런데 여기에는 약간 불편한 점들이 있다.

  • Data2 클래스와 관련된 일인데, Counter 라는 별도의 클래스를 추가로 사용해야 한다.
  • 생성자의 매개변수도 추가되고, 생성자가 복잡해진다. 생성자를 호출하는 부분도 복잡해진다.

 

 

static 변수 사용

특정 클래스에서 공용으로 함께 사용할 수 있는 변수를 만들 수 있다면 편리할 것이다.
static 키워드를 사용하면 공용으로 함께 사용하는 변수를 만들 수 있다.

Data3

package static1;

public class Data3 {
    public String name;
    public static int count;  // static

    public Data3(String name) {
        this.name = name;
        count++;
    }
}

기존 코드를 유지하기 위해 새로운 클래스 Data3 을 만들었다.
객체가 생성되면 생성자에서 정적 변수 count 의 값을 하나 증가시킨다.

static int count 부분을 보자. 변수 타입 (int) 앞에 static 키워드가 붙어있다.
이렇게 맴버 변수에 static 을 부이게 되면 static 변수, 정적 변수 또는 클래스 변수라 한다.

 

DataCountMain3

package static1;

public class DataCounterMain3 {
    public static void main(String[] args) {
        Data3 d1 = new Data3("data1");
        Data3 d2 = new Data3("data2");
        Data3 d3 = new Data3("data3");

        System.out.println(Data3.count);
    }
}
3

static 이 붙은 맴버 변수는 메서드 영역에서 관리된다.
static 이 붙은 맴버 변수 count 는 인스턴스 영역(힙 영역)에 생성되지 않는다. 대신에 메서드 영역에서 이 변수를 관리한다.

 

 

static 변수 용어 정리

Data3

package static1;

public class Data3 {
    public String name;
    public static int count;  // static

    public Data3(String name) {
        this.name = name;
        count++;
    }
}

예제 코드에서 name, count 는 둘다 맴버 변수이다.
맴버 변수(필드)는 static 이 붙은 것과 아닌 것에 따라 다음과 같이 분류할 수 있다.

 

맴버 변수(필드)의 종류

  • 인스턴스 변수: static 이 붙지 않은 맴버 변수, 예) name
    • static 이 붙지 않은 맴버 변수는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다. 따라서 인스턴스 변수라 한다.
    • 인스턴스 변수는 인스턴스를 만들 때 마다 새로 만들어진다.
  • 클래스 변수: static 이 붙은 맴버 변수, 예) count
    • 클래스 변수, 정적 변수, static 변수등으로 부른다. 용어를 모두 사용하니 주의하자.
    • static 이 붙은 맴버 변수는 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다. 따라서 클래스 변수라 한다.
    • 클래스 변수는 자바 프로그램을 시작할 때 딱 1개가 만들어진다. 인스턴스와는 다르게 보통 여러곳에서 공유하는 목적으로 사용된다.

 

변수와 생명주기

  • 지역변수(매개변수 포함): 지역 변수는 스택 영역에 있는 스택 프레임 안에 보관된다. 메서드가 종료되면 스택 프레임도 제거 되는데 이때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다. 따라서 지역 변수는 생존 주기가 짧다.
  • 인스턴스 변수: 인스턴스에 있는 맴버 변수를 인스턴스 변수라 한다. 인스턴스 변수는 힙 영역을 사용한다. 힙 영역은 GC(가비지 컬렉션)가 발생하기 전까지는 생존하기 때문에 보통 지역 변수보다 생존 주기가 길다.
  • 클래스 변수: 클래스 변수는 메서드 영역의 static 영역에 보관되는 변수이다. 메서드 영역은 프로그램 전체에서 사용하는 공용 공간이다. 클래스 변수는 해당 클래스가 JVM에 로딩 되는 순간 생성된다. 그리고 JVM이 종료될 때 까지 생명주기가 이어진다. 따라서 가장 긴 생명주기를 가진다.

static 이 정적이라는 이유는 바로 여기에 있다. 힙 영역에 생성되는 인스턴스 변수는 동적으로 생성되고, 제거된다. 반면에 static 인 정적 변수는 거의 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다. 정적 변수는 이름 그대로 정적이다.

 

 

정적 변수 접근법

package static1;

public class DataCounterMain3 {
    public static void main(String[] args) {
        Data3 d1 = new Data3("data1");
        Data3 d2 = new Data3("data2");
        Data3 d3 = new Data3("data3");

        // 클래스를 통한 접근
        System.out.println(Data3.count);
        // 인스턴스를 통한 접근
        System.out.println(d1.count);
        System.out.println(d2.count);
        System.out.println(d3.count);
    }
}
3
3
3
3

static 변수는 클래스를 통해 바로 접근할 수도 있고, 인스턴스를 통해서도 접근할 수 있다.

그런데 인스턴스를 통한 static 변수는 추천하지 않는다. 왜냐하면 코드를 읽을 때 마치 인스턴스 변수에 접근하는 것 처럼 오해할 수 있기 때문이다.

0 0
JAVA
이 공간은 개인 공부를 통해 얻은 정보를 체계적으로 정리하고 공유하는 곳입니다. 학습한 내용, 발견한 지식, 그리고 문제 해결 방법 등을 기록하여 나만의 학습 자료를 구축하고, 필요할 때 쉽게 참고할 수 있는 유용한 자원으로 활용할 수 있도록 합니다.
Yesterday: 456
Today: 236