[Java] Static이란?

임재영·2025년 5월 28일
post-thumbnail

정적(Static)이란?


'정적인', '고정적인' 이라는 사전적 의미를 가진다.
이를 Java 관점으로 해석하면, '클래스의' 또는 '공통적인' 이라는 의미를 가진다.

Static을 이해하기 위해서는 먼저, JVM(자바 가상 머신)을 이해할 필요가 있다.

JVM(Java Virtual Machine, 자바 가상 머신)

쉽게 말하면, "자바를 실행하기 위한 가상의 컴퓨터"라고 할 수 있다.

  1. Java 애플리케이션을 실행하면 JVM은 OS로부터 메모리를 할당한다.
  2. 자바 컴파일러(javac)가 자바 소스코드(Xxx.java)를 바이트코드(Xxx.class)로 컴파일한다.
  3. 클래스 로더(Class Loader)를 통해 JVM 메모리(Runtime Data Areas)로 로딩한다.
  4. 로드된 클래스 파일(Xxx.class)들은 기계가 읽을 수 없으므로, 실행 엔진(Execution Engine)을 통해 기계어로 변환하여 실행한다.
  5. 이 과정에서 실행 엔진에 의해 가비지 콜렉터(Garbage Collector) 등도 작동된다.

자바 애플리케이션이 실행되면 JVM은 시스템으로부터 필요한 메모리를 할당받고, 용도에 따라 여러 영역으로 나누어 관리한다. Method Area, Call Stack, Heap이 대표적이다.

  • 메서드 영역 (Method Area)
    • JVM이 클래스 파일 (*.class)을 읽어 클래스 데이터를 저장하는 영역이다. 이때, 클래스 변수도 함께 생성된다.
  • 힙 (Heap)
    • 인스턴스가 생성되는 공간이다. 프로그램 실행 중 new 연산으로 생성되는 인스턴스 변수들이 생성된다.
  • 호출 스택 (Call Stack)
    • 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면 호출 스택에 메모리가 할당되고, 해당 메서드가 수행되는 동안 필요한 값 등을 저장하는 데 사용된다. 메서드 실행이 완료되면 할당되었던 메모리 공간은 반환된다.



자바의 Static 키워드


자바에서 static 키워드는 클래스 레벨의 변수메서드, 블록을 정의할 때 사용된다.

특징

  • 인스턴스 생성 없이도 접근이 가능하다.
  • 모든 인스턴스에서 공유된다.
  • static 변수는 프로그램이 시작할 때 메모리에 할당되고, 프로그램이 종료될 때까지 유지된다.

어떻게 가능할까?

왜냐하면, static 멤버는 클래스가 로드될 때 메모리의 메서드 영역에 할당되기 때문이다. 이는 모든 인스턴스가 공유하는 특성 때문에 유용하게 사용될 수 있다.
예를 들어, 어떤 클래스의 인스턴스들이 공통적으로 사용해야 하는 값을 static변수로 선언할 수 있다.

Static 키워드의 단점

static 키워드의 남용은 객체 지향 프로그래밍의 원칙과 상반되며, 메모리 사용량 증가로 이어질 수 있다. 따라서, static 멤버의 사용은 신중하게 결정해야 한다.
👉 static 멤버는 프로그램 종료 시 까지 메모리에 남아 있어, 과도한 static 사용은 메모리 누수의 원인이 될 수 있기 때문이다. 이는 특히, 대규모 애플리케이션에서 성능 저하의 원인이 될 수 있다.



정적(Static) 변수란?


  • 클래스 변수
  • 클래스 레벨에서 선언되며, 모든 인스턴스에 의해 공유된다.
  • 한 클래스에서 공통적인 값을 유지해야할 때 선언한다.
  • 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지된다.
  • 객체를 생성하지 않고도 '클래스이름.변수명'으로 호출이 가능하다.



❗정적 변수(static variable)와 전역 변수(global variable)의 혼동

static 변수의 설명을 보고 처음 든 생각은 "전역 변수 아니야? 전역 변수랑 다른게 뭐지?" 였다.
바로 본론부터 말하자면, 자바에는 전역 변수라는 개념이 없다.
C, Python에서 처럼 특정 클래스에 속하지 않는 전역 변수의 개념은, 모든 변수가 클래스 안에서 정의되어야 하고, 특정 클래스에 종속되지 않은 변수를 선언할 방법이 없는 자바와는 반대되는 개념이기 때문이다.
만약 자바에서 정적 변수를 전역 변수처럼 사용해야 한다면, 싱글톤 패턴(Singleton Pattern)이나 private static변수와 getter/setter를 활용하는 방법이 있다.



(Static 변수) 예시 코드

// static 변수 (클래스 변수)
public class MyMathStaticBasic {
	public static final String DESCRIPTION = "static 변수";
}

// 인스턴스 변수
public class MyMathBasic {
	public long a;
	public long b;
	public String description = "인스턴스 변수";
}

테스트

@Test
@DisplayName("static 변수, 인스턴스 변수")
public void staticStr() {
	// static 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
	System.out.println("static 변수 출력 = " + MyMathStaticBasic.DESCRIPTION);
	
	// 인스턴스 변수는 인스턴스를 생성해야 사용할 수 있다.
	MyMathBasic mathBasic = new MyMathBasic();
	System.out.println("인스턴스 변수 출력 = " + mathBasic.description);
}

결과

 static 변수
인스턴스 변수 출력 = 인스턴스 변수


정적(Static) 메서드란?


  • 클래스 메서드
  • 클래스의 다른 static메서드나 static변수만 접근할 수 있다. 그러므로 인스턴스 멤버에 접근해야 하는 경우에는 사용할 수 없다.
  • 객체를 생성하지 않고도 '클래스이름.메서드명'으로 호출이 가능하다.
    ➡️ 유틸리티 함수나 상태가 필요 없는, 예를 들어 수학 연산을 수행하는 메서드의 경우에는 static으로 선언하는 것이 유용하다.
    왜냐하면, static메서드는 인스턴스의 상태에 의존하지 않는 연산을 수행할 때 유용하기 때문이다. 이는 메서드 호출 시 인스턴스 생성의 오버헤드 없이 빠른 실행이 가능하다.



(Static 메서드) 예시 코드

// static 변수, static 메서드 (클래스 변수, 클래스 메서드)
public class MyMathStaticBasic {
	public static final String DESCRIPTION = "static 변수";
	
	/** 매개변수로 가능 */
	public static long add(long a, long b) {
		return a + b;
	}
	public static long substract(long a, long b) {
		return a - b;
	}
	public static long multiply(long a, long b) {
		return a * b;
	}
	public static long divide(long a, long b) {
		return a / b;
	}
}

// 인스턴스 변수
public class MyMathBasic {
	long a;
	long b;
	String description = "인스턴스 변수";
	
	/** 인스턴스 변수 a, b만 이용하므로 매개변수가 필요 없다. */
	public long add() {
		return a + b;
	}
	public long substract() {
		return a - b;
	}
	public long multiply() {
		return a * b;
	}
	public long divide() {
		return a / b;
	}
}

테스트

@Test
@DisplayName("static 메소드")
public void mathStatic() {
    long num1 = 100L;
    long num2 = 10L;
    
    System.out.println("add(num1, num2) = " + MyMathStaticBasic.add(num1, num2));
    System.out.println("subtract(num1, num2) = " + MyMathStaticBasic.subtract(num1, num2));
    System.out.println("multiply(num1, num2) = " + MyMathStaticBasic.multiply(num1, num2));
    System.out.println("divide(num1, num2) = " + MyMathStaticBasic.divide(num1, num2));
    
    assertEquals(110L, MyMathStaticBasic.add(num1, num2));
    assertEquals(90L, MyMathStaticBasic.subtract(num1, num2));
    assertEquals(1000L, MyMathStaticBasic.multiply(num1, num2));
    assertEquals(10.0, MyMathStaticBasic.divide(num1, num2));
}

@Test
@DisplayName("인스턴스 메소드")
public void mathBasic() {
    MyMathBasic mathBasic = new MyMathBasic();
    mathBasic.a = 200L;
    mathBasic.b = 10L;
    
    System.out.println("add() = " + mathBasic.add());
    System.out.println("subtract() = " + mathBasic.subtract());
    System.out.println("multiply() = " + mathBasic.multiply());
    System.out.println("divide() = " + mathBasic.divide());
    
    assertEquals(210L, mathBasic.add());
    assertEquals(190L, mathBasic.subtract());
    assertEquals(2000L, mathBasic.multiply());
    assertEquals(20.0, mathBasic.divide());
}

결과 (static 메서드)

add(num1, num2) = 110
subtract(num1, num2) = 90
multiply(num1, num2) = 1000
divide(num1, num2) = 10.0

결과 (인스턴스 메서드)

add() = 210
subtract() = 190
multiply() = 2000
divide() = 20.0


정적(Static) 블록이란?


  • 클래스가 처음 로드될 때 한 번만 실행되는 코드 블록이다.
  • 클래스 로딩 시점에 단 한 번만 실행되기 때문에, 복잡한 초기화 작업에 적합하다. 이는 프로그램 시작 시 필요한 리소스를 준비하는 데 유용할 수 있다.
  • static 블록 내에서 예외를 발생시킬 경우 프로그램의 실행에 영향을 줄 수 있으므로, 예외 처리에 주의해야 한다.
    ➡️ static블록의 예외는 프로그램 초기화 과정에 영향을 줄 수 있어 프로그램의 안정성을 해칠 수 있다.
    따라서 static블록의 사용은 필요한 경우에만 제한적으로 이루어져야 한다.



(Static 블록) 예시 코드

public class AppConfig {
    static {
        // 초기화 코드
    }
}


📌 정리

  1. 클래스를 설계할 때, 멤버 변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 선언한다.
  2. 클래스 변수(static 변수)는 인스턴스를 생성하지 않아도 호출할 수 있다.
  3. 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.
  4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려하자.



마치며

자바에서 static 키워드는 유용하지만, 그 사용은 신중해야 한다.
static 멤버의 남용은 객체 지향 프로그래밍 원칙에 어긋난다. 또한, 프로그램의 생명주기 동안 메모리에 상주하므로 불필요한 메모리 사용을 초래할 수 있기 때문에, 메모리 관리 측면에서도 부정적인 영향을 줄 수 있다. 따라서, 필요한 경우에만 제한적으로 이루어져야 한다.
적절한 경우에 static을 사용하면 코드의 재사용성과 효율성, 안정성을 높일 수 있다. 객체 지향의 원칙과 메모리 관리를 고려하여, static을 올바르게 사용하자.




[참고]
https://gymdev.tistory.com/73
https://f-lab.kr/insight/understanding-and-using-java-static-keyword?gad_source=2&gad_source=1&gclid=Cj0KCQiAq-u9BhCjARIsANLj-s2gaTT9B1lur1FTJPIfzaSaWpayPS9CS_9tjJvPEzyDAIUEJ9GWuzcaAhuuEALw_wcB

0개의 댓글