[Java] static에 대하여

bien·2023년 10월 25일
0

우아한테크코스

목록 보기
2/3

Non-static field '인스턴스' cannot be referenced from a static contex

static과 관련해서 위와같은 오류 메시지를 흔하게 만날 수 있다. 왜 이러한 오류 문구가 나타나는지, static이란 어떤 녀석인지 자세히 알아보자!

📖 static이란?

public class MyClass {
    public static int classVariable; // 클래스 변수 또는 static 변수

    public int instanceVariable; // 인스턴스 변수
    
    void method() {
    	int localvariable; // 지역 변수
    }
}
  • static 변수 (static variable) (= 클래스 변수)
    • 변수에 static 키워드를 붙여 사용하며, 메모리 할당을 딱 한번만 하게 된다.
    • 모든 변수가 공통된 저장공간을 공유하게 된다.
  • static 메서드 (static method) (=클래스 메서드)
    • 객체 생성 없이 클래스이름.메서드이름()으로 호출 가능한 메서드
    • 인스턴스 멤버와 관련없는 작업을 수행하며, 메서드 내에서 인스턴스 변수를 사용할 수 없다.
  • 싱글톤 패턴 구현에 사용된다.

static을 언제 붙여야 할까?

  • 속성(멤버 변수)중에서 공통 속성에 static을 붙인다
  • 인스턴스 멤버(instance variable, instance method)를 사용하지 않는 메서드에 static을 붙인다.

🤔 의문점

Q. 왜 static 메서드 내부에서 인스턴스 멤버에 접근할 수 없을까?
Q. 어떻게 공통된 저장공간을 공유할 수 있는걸까?

흐름을 봤을 때, 이유는 '생성 순서'로 짐작이 간다. 그럼 정확히 어떤 이유로 static의 사용이 위와 같은 특성을 가지는지 자세히 알아보자!


📖 Java의 작동원리

Java의 작동원리에 대해 Java 실행원리 Deep Dive 글에서 너무 훌륭하게 정리해주셨다. 필요하면 모두 읽어보면 좋을 것 같고, 나는 여기서 static과 관련된 부분에만 집중해보려고 한다.

📁 JAVA의 실행 전반

  • .java파일은 javac에 의해 .class 파일로 컴파일된다.
  • 컴파일된 .class파일은 JVM의 Class Loader에 의해 JVM의 메모리 영역(Runtime Data Area)에 로딩된다.
  • JVM 메모리에 로딩된 후 interpreter에 의해 운영체제가 이해할 수 있는 기계어로 변환된다.
  • 운영체제의 적절한 함수를 호출하여 필요한 로직을 수행한다.

자바 프로그램 실행흐름을 따라가면서 static의 특징을 가지게 되었는지 살펴보자!

  • Class Loader Subsystem에서 static의 생성 순서를 확인할 수 있고,
  • Runtime Data Area에서 저장 공간의 차이에 대해 확인할 수 있다.

Class Loader Subsystem

Class Loader Subsystem의 주목적은 필요한 class를 찾아 JVM 메모리에 로드하고 연결(link)하기 위함이다.

이 과정을 가볍게 살펴보자.

1. Load (Class Loaders)

java 프로그램 실행에 필요한 클래스를 jvm에 로드한다.

  • Bootstrap class loader: JVM이 실행될 때 해당 java 프로그램 실행에 필요한 기본적인 class들을 로드한다
  • Extension class loader: Extension class(JDK가 추가적으로 제공하는 라이브러리)들을 로드한다.
  • Application class loader: classpath에 설정된 class를 로드한다.

.class 파일 내의 symbolic reference를 실제 메모리 주소로 변환하는 작업을 수행한다.

symbolic reference
우리가 코드를 작성하면서 사용한 class, field, method의 이름. Resole단계에서 class, field, method 그리고 constant pool의 references를 실제 메모리 주소로 변경한다.

  1. Verify: .class파일이 java 스펙에 맞는지 검증한다. Bytecode format, version number등을 확인한다.
  2. Prepare: 클래스와 static variable을 위한 메모리를 할당한다. 이때 static variable이 default값으로 초기화된다.
  3. Resolve: symbolic reference(우리가 부여한 이름)를 실제 메모리 주소로 변환한다.

3. Initialize

Static initializer block을 실행한다. 위 link prepare 단계에서는 static 변수가 default 값으로 초기화 되었으나, 이 단계에서 개발자가 지정한 값으로 static 변수가 설정된다. Initialize가 완료된 이후 main 메서드가 실행된다.

즉, 앞서 link단계에서 class와 static variable을 위한 메모리 공간이 준비되고, 초기화Ilitialize 단계에서 static 변수가 할당된 이후, main 메서드가 실행된다.

인스턴스 변수는 언제 준비될까? 다들 알다시피 main 메서드가 시행된 이후에 코드를 따라 내려가면서 인스턴스 변수들이 생성되고 소멸하게 된다.

즉, static 변수 생성 > main 메서드 실행 >인스턴스 변수 생성의 순서를 따르므로 static 내부에서 인스턴스 멤버에 접근할 수 없다!

📁 JVM의 메모리 구조

  • Method Area(메서드 영역)
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다. 이 때, 그 클래스의 클래스 변수(class variable)도 이 영역에 함께 생성된다.
  • Heap Area(힙 영역)
    • 인스턴스가 생성되는 공간. 인스턴스 변수(instance variable)가 생성되는 공간이다.

static variable은 method area에, insatance variabe은 heap에 저장된다. 이때, 각 인스턴스에 포함된 static 변수들이 모두 Method Area에 할당된 같은 주소를 가리키고 있기 때문에, 값을 공유하며 일괄적으로 변경된다!

주의!

Heap영역은 Garbage Collector를 통해 관리가 되지만, Static 영역의 메모리는 관리되지 않는다. static 영역에 할당된 정보는 프로그램의 종료시 까지 메모리에 할당된 채로 존재하기 때문에, 너무 자주 사용해서는 안된다!


Reference

profile
Good Luck!

0개의 댓글