[JAVA] 정말 static키워드는 GC의 영향을 받지 않을까?

Y_Sevin·2024년 6월 18일
3
post-thumbnail

Java 면접 질문 리스트를 보면 항상 등장하는 키워드 중 하나가 static이다. 많은 블로그의 글들을 보면 Static은 클래스가 로딩될 때 메모리에 할당되고 프로그램 종료 시까지 존재한다는 말을 자주 볼 수 있다. 그러나 이 말에는 약간의 오점이 있는 것을 알게되었고, 해당 오점을 학습하며 공부한 내용을 공유해보고자 한다.

Static이란?

Java에서 static 키워드는 주로 메모리 관리를 위해 사용된다. static 키워드를 사용하면 해당 변수나 메소드는 클래스 레벨에서 사용할 수 있으며, 인스턴스를 생성하지 않고도 접근할 수 있다. 이는 메모리 사용을 최적화하고 전역적으로 접근 가능한 데이터를 만드는 데 유용하기에, 유틸리티 메서드나 싱글톤 패턴 등에서 주로 사용하곤 한다.

Java 7까지의 Static 관리: Permgen 영역

그렇다면 이 Static키워드는 어떻게 관리했을까?
Java 7까지는 Heap 영역에 영구적인 저장공간이라는 의미의 Permgen이라는 영역이 있었다. Permgen은 JVM에 의해 관리되는 특별한 힙 공간으로, 클래스 메타데이터, static 변수, 상수 등을 저장하는 공간이며 일반적인 객체 인스턴스와는 다른 방식으로 관리되는 영역이다.

Permgen 영역에서의 Static 문제

Permgen 영역에는 static 변수와 메소드와 관련된 여러 문제가 있었다. Static 변수와 메소드는 클래스 레벨의 변수로, 모든 클래스 인스턴스에서 공유된다. 따라서, 이들의 생명 주기는 클래스가 로딩된 시점부터 프로그램이 종료될 때까지다. 즉, static 변수는 한 번 메모리에 할당되면 계속해서 메모리를 차지하게 된다.

하지만 Permgen은 런타임에 크기 조정이 불가능한 고정되어 있는 Heap영역에 속해있다. 때문에, 갑자기 많은 클래스가 생성되고 static 데이터가 남아있다면 OutOfMemoryError가 발생할 수 있었다. 이는 특히 대규모 애플리케이션이나 동적으로 많은 클래스를 로드하는 애플리케이션에서 심각한 문제가 된다.

java.lang.OutOfMemoryError: PermGen space

Java 8부터의 Static 관리: Metaspace 영역

이러한 문제를 해결하기 위해, Java 8에서는 Permgen 영역을 Metaspace로 대체했다. Metaspace는 네이티브 메모리에 할당되어 Permgen의 고정된 크기라는 단점을 보완하고 동적 메모리 할당을 가능하게 했다.

그리고 static object를 Java Heap으로 이동시켰다. 이를 통해 Garbage Collector (GC)의 관리 하에 들어가 메모리 효율성을 향상시켰다.

참고로 모든 static이 아니라 reference형식의 static object만 heap영역에 저장되는 것이다. 그리고 reference는 여전히 Metaspace에서 관리된다.

정리

  • 클래스 및 메소드 메타정보: 네이티브 메모리인 Metaspace로 이동
  • Static object: Java Heap으로 이동, GC의 대상이 됨

네이티브 메모리(Native Memory)란?
JVM 바깥에서 할당되고 관리되는 메모리이다. (시스템의 기본 메모리)
JVM이 아닌 운영 체제의 일부로 관리되며 OS가 자동으로 크기를 조절한다. 동적으로 크기를 관리하기에 Permgen의 크기가 고정되어있다는 문제를 해결할 수 있다.

Static 과 GC

Static Object는 Java Heap에 있지만, 여전히 클래스 레벨 변수로서 클래스가 로딩되어 있는 한 메모리에 유지된다. GC는 일반적으로 클래스가 참조되고 있는 한 static 변수를 회수하지 않는다. 이는 static 변수의 특성과 클래스 로더의 동작 방식 때문이다.

  1. GC ❌:Static 변수: Static 변수는 클래스에 속하기 때문에 클래스가 로딩되어 있는 한 유지된다. GC는 일반적으로 클래스가 참조되고 있는 동안 static 변수를 회수하지 않는다. 이는 static 변수가 클래스의 상태를 나타내는 중요한 정보일 수 있기 때문이다.

  2. GC ⭕:클래스 언로딩: 특정 조건 하에서, 예를 들어 클래스 로더가 더 이상 참조되지 않을 때 (주로 사용자 정의 클래스 로더나 OSGi와 같은 동적 모듈 시스템에서 발생), 클래스와 해당 static 변수가 언로딩되며 메모리가 회수될 수 있다. 그러나 이는 매우 특수한 상황에서만 발생한다고 한다.

  3. GC ⭕:static object의 참조가 없는 경우: 더 이상 어떤 객체도 Static Object를 참조하지 않을때 GC의 대상이 된다. (의도적으로 참조변수에 null 을 대입한 경우)

❌❌❌java 8부터 Static object가 GC영향을 받는다는 말이구나!...?

여기까지 글을 읽었다면 한가지 오해가 생길 수 있다. java 8부터 Static object가 GC영향을 받는다는 오해말이다...
Permanent Generation은 말 그대로 영구적인 영역이라는 말이다. 그래서 많은 사람들이 Java 8 이후로 해당 영역이 사라지고 Heap 영역에 static object가 관리되니 Java 8 이후로 GC영향을 받는다고 생각하는 것 같다. 왜냐하면 필자가 블로그를 보며 공부했을때 해당 오해가 생겼었다...😓

하지만 Java 8이전에도 permgen 영역에 GC가 동작했다. 영구적인 영역이라는 말과 달리 모순적이지만 과거 자료들을 찾아보니 동작했었다는 사실을 알게되었다.

정말 과거인 Java 5에서는 기본적으로 permGen을 수집하지는 않았다. XX:+CMSPermGenSweepingEnabled 를 사용해 활성화해야만 GC가 동작했기 때문이다.
하지만 아래 문서를 확인하면 Old Generation이 GC의 영향을 받을 때 Perm Gen 영역도 영향을 받는다는 사실을 알 수 있다. Perm Gen영역은 java 8이후로 사라졌으니 결과적으로 java 8 이전에도 gc의 영향을 받았던 것을 알 수 있다.

https://www.oracle.com/java/technologies/visual-garbage-collection-monitoring-tool.html

왜 이런 오해가 생겼을까?

실제로 해당 오류를 경험해 보지 않았기에, 스택오버플로우 답변을 인용해서 구체화한 내용을 기술하였습니다. - 해당 내용은 사실이 아닐 수 있으니 참고만 해주시면 좋겠습니다.

java.lang.OutOfMemoryError: PermGen space

아마 이런 오래가 생긴 이유는 위의 예외를 permgen 영역이 전혀 수집되지 않는다고 잘못 이해한 것이 가장 큰것 같다.

사실 PermGen(Permanent Generation) 영역에서 발생하는 OutOfMemoryError의 주요 원인은 JVM이 실행 중에 코드를 핫 로드할 때 발생하는 메모리 누수가 가장 크다.
핫 로드는 프로그램이 실행 중에 새로운 코드를 동적으로 로드하여 기존의 코드를 대체하는 과정을 말한다. 예를 들어 말하자면, 웹 애플리케이션에서 새로운 버전의 자바 클래스 파일을 업데이트하는 과정에서 문제가 발생한다는 말이다.

해당 과정을 좀더 자세히 알아보자면 아래와 같다.

  1. 클래스 메타데이터 저장
    JVM은 각 클래스를 로드할 때, 해당 클래스의 구조와 관련된 정보를 PermGen 영역에 저장한다. 이 정보에는 클래스의 메소드, 변수, 상수 등이 포함된다.

  2. 클래스 언로드 문제
    핫 로드로 새로운 클래스를 로드하면 이전 버전의 클래스 인스턴스들이 더 이상 사용되지 않게 된다. 하지만 JVM은 이전 버전 클래스의 인스턴스들이 여전히 메모리에서 참조되고 있어야 해제되지 않는다.

  3. 메모리 누수
    이전 버전의 클래스 인스턴스들이 메모리에서 해제되지 않고 남아 있으면, PermGen 영역의 메모리가 점진적으로 소진된다. 이 과정에서 메모리 부족 오류(OutOfMemoryError)가 발생할 수 있다.

이렇게 누수된 객체의 대부분은 permgen 영역에 있기 때문에 OutOfMemoryError 가 발생했지만 permgen 영역에 GC가 동작하지 않아 오류가 발생했다는 오해를 가지는 것 같다.

결론

  • static Object는 참조를 잃으면 GC의 대상이 된다.
  • java 8이후로 Permgen이 Metaspace로 대체되며 메모리 관리가 용이해졌다.
  • static Object의 gc는 java 8이전에도 동작했다. 하지만 old Generation 영역이 GC의 영향을 받을때 동작하기에 오랫동안 살아있다.

참고
https://stackoverflow.com/questions/3796427/in-java-is-permanent-generation-space-garbage-collected

profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎

0개의 댓글