[Java] static이란?

이신영·2024년 6월 11일
1

Java

목록 보기
8/11
post-thumbnail

우리는 스프링, 혹은 자바를 사용해서 코드테스트를 풀면서 static이라는 키워드를 종종 써왔다. 대충 전역적으로 선언하는건 알고있는데 세세한 부분까지는 모른다.(나는 그렇다 ㅎ;)

static으로 선언된 변수나 메서드 혹은 클래스들이 어느시점에서 어디에 저장되고 어느시점에 해제된다던지?

이를 자세히 알아보기위해 static이 뭔지를 다뤄보도록 하자~


static이란?

먼저, static 키워드는 클래스에서 변수나 메서드를 선언할 때 사용된다.

일반적으로 변수나 메서드를 선언하면, 객체가 생성될 때마다 각각의 인스턴스마다 그 변수나 메서드가 따로따로 생성된다.

하지만 static으로 선언된 변수나 메서드는 클래스 로딩 시 한 번만 생성되고, 모든 인스턴스가 이 하나의 static 멤버를 공유하게 된다.

그래서 왜 씀 ?

한마디로 정리하자면 객체를 생성하지않고 사용해야할 때 쓴다!

공유자원이 필요하다거나 인스턴스없이 접근해야할 때 쓸 수 있다.

왜 인스턴스없이 접근이 가능?

일반적으로 객체는 클래스의 인스턴스이다. 즉, 클래스를 기반으로 생성되는 실제 데이터의 모임이라고 할 수있다. 각각의 객체는 독립적인 메모리 공간을 가지며, 인스턴스 변수와 인스턴스 메서드도 이러한 독립적인 메모리 공간에 존재한다.

하지만, static을 붙이게 되면 static 변수나 메서드는 클래스 로딩 시 생성되어 클래스 수준의 메모리 영역에 할당된다. 따라서 static 변수나 메서드는 클래스 자체에 속하게 된다. 이는 모든 인스턴스가 동일한 static 멤버를 공유하며, 객체를 생성하지 않고도 클래스 이름을 통해 직접 접근할 수 있다는 것을 의미한다.

public class Example {
    // static 변수
    public static int staticVar = 42;

    // static 메서드 1
    public static void staticMethod1() {
        System.out.println("Static method 1 called");
        System.out.println("Accessing staticVar: " + staticVar);
        staticMethod2(); // 다른 static 메서드 호출
    }

    // static 메서드 2
    public static void staticMethod2() {
        System.out.println("Static method 2 called");
    }

    // main 메서드
    public static void main(String[] args) {
        Example.staticMethod1(); // 클래스 이름으로 직접 호출
    }
}

요약하자면

클래스 로딩 시 한 번만 메모리에 할당되어 모든 인스턴스가 공유하기 때문에 메모리 사용 효율이 높아진다!


그래서 어따 씀?

1. 공유자원

public class Counter {
    public static int count = 0;

    public Counter() {
        count++;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        System.out.println(Counter.count); // 2 출력
    }
}

동일한 데이터나 자원을 여러 인스턴스에서 공유할 때 메모리를 절약할 수 있다.

2. 객체 생성 없이 호출이 필요할 때

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3);
        System.out.println(sum); // 8 출력
    }
}

객체를 생성하지 않고도 add를 호출할 수 있다. 이를 통해 자주 사용되는 유틸리티 메서드를 정의할 때 유용하다.

3. 싱글턴 패턴 구현

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
        // private 생성자
    }

    public static Singleton getInstance() {
        return instance;
    }
}

public class Main {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // true 출력
    }
}

즉, 하나의 인스턴스가 보장되어야 할 때 이다. static 변수를 사용하여 유일한 인스턴스를 저장하고, static 메서드를 통해 접근할 수 있다.

4. 네임 스페이스 관리

네임스페이스?

개체를 구분할 수 있는 범위를 나타내는 말로 만약 com.example.util패키지와 com.example.math패키지 모두 동일한 이름을 가지고 있는 클래스가 정의되어있으면 패키지를 통해 이름 충돌을 방지한다. 즉, 패키지가 네임스페이스를 관리하는상황이다.

public class Example {
    // 클래스 변수 (static 변수)
    public static int count = 0;

    // 인스턴스 변수
    public int count = 10;

    public static void incrementCount() {
        // static 메서드에서 클래스 변수에 접근
        count++;
    }

    public void displayCounts() {
        // 인스턴스 메서드에서 클래스 변수와 인스턴스 변수 모두 접근 가능
        System.out.println("Static count: " + Example.count);
        System.out.println("Instance count: " + this.count);
    }

    public static void main(String[] args) {
        Example.incrementCount();
        
        Example example = new Example();
        example.displayCounts();
    }
}

패키지 뿐만 아니라 static을 사용해서 동일한 클래스 내에 count로 선언한 두 변수가 존재할 수 있다.

5. 상수 정의

public class Constants {
    public static final double PI = 3.14159;
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Constants.PI); // 3.14159 출력
    }
}

final 정리를 참고해보도록하자 ㅎ.. 아무튼 static을 붙였으니 단 한번 클래스로딩때 할당, 초기화되고 final을 붙였으니 변하지 않는다. 이는 불변을 뜻할 수 있다!

6. 정적 내부 클래스

public class OuterClass {
    private static int staticOuterValue = 10;
    private int instanceOuterValue = 20;

    public static class StaticNestedClass {
        public void display() {
            // 외부 클래스의 static 멤버에 접근 가능
            System.out.println("Static outer value: " + staticOuterValue);

            // 외부 클래스의 인스턴스 멤버에 접근 불가 (컴파일 오류)
            // System.out.println("Instance outer value: " + instanceOuterValue);
        }
    }

    public static void main(String[] args) {
        StaticNestedClass nested = new StaticNestedClass();
        nested.display();
    }
}

클래스 내의 클래스를 만들 때 외부 인스턴스에 접근할 일이 없을 때 만들 수 있다. 만드는이유? 메모리 누수 방지겠지요?


번외) Java8부터는 static이 heap영역에 저장된다?

static은 런타임시 클래스 로더에 의해 메서드 영역에 적재되며 프로그램이 종료될 때 까지 메모리가 해제되지 않는다고 알고있었는데..?

JDK8부터는 permanent generation (permgen)이 Metaspace로 변경되었고 그에따라 static도 Metaspace에 저장된다고 한다. 달라지는게 있을까?

permanent과 metaspace?

먼저 permanent영역과 metaspace영역에 대해서 정리해보자.

permanent

permanent영역은 클래스 내부의 메타 데이터를 저장하는 영역이다.

  • Class Meta data
  • Method Meta data
  • Static Object
  • Class 관련 배열 객체 및 객체 Meta 정보
  • JVM 내부적인 객체들과 최적화컴파일러(JIT)의 최적화 정보

metaspace

metaspace영역은 네이티브 메모리영역으로 JVM이 실행될 때 운영 체제에서 할당받는 메모리이다.

이것들은 Metaspace가 생기면서 대부분 Metaspace에 저장되지만? 이 중에 Static Object이 Heap영역으로 이동하게되었다.

그럼 static도 GC의 대상일까?

static 변수와 메서드는 Metaspace (Java 1.8 이후) 또는 PermGen (Java 1.7 이전) 영역에 생성되며, 인스턴스를 생성하지 않아도 클래스 이름을 통해 접근할 수 있다.

Java 1.7 이전에는 PermGen 영역의 static 변수는 GC의 대상이 아니었으나, Java 1.8 이후 Metaspace로 바뀌면서 GC의 대상이 될 수 있다.

근데 왜 바뀜?

static object가 문제를 일으켜서 바뀌었다.

가끔씩 Collection 객체를 static하게 구현하여 값을 계속해서 추가하다가 Perm영역이 가득차서 메모리 누수가 발생했을때나

로딩된 클래스와 클래스 로더가 종료될때 이것들이 GC되지 않았을때 발생한다고 한다. 그래서 수정될 필요가 없는 정보만 Metaspace에 저장하도록 바뀌었다.

즉? 사이즈제한도 없애고 GC오류도 고치려고 바뀜


profile
후회하지 않는 사람이 되자 🔥

0개의 댓글