본문 바로가기

Language/JAVA

[JAVA] 자바 8 이후 스태틱 객체가 GC 대상이 된 이유 - 컴도리돌이

728x90


 

자바에서 스태틱 객체는 전역으로 사용할 수 있고, 코드 내에서 어디서든 참조할 수 있습니다.

그리고 스태틱에 대해서 정리하면서 스태틱은 GC의 대상이 아니기 때문에(스태틱은 객체 생성이 아니기 때문에), 무분별한 사용을 하면 안 된다고 알고 있었습니다. 하지만 이 내용은 java 8 버전 이전의 내용이었고, 자바 8부터는 스태틱 객체도 GC의 대상이 되었고, GC에 대한 간단한 설명과 스태틱 객체가 왜 GC의 대상이 되었는지 기록하려고 합니다. 

 

참고로 java 8은 2014년 3월에 출시되었습니다. 🤒

 

java 8 이전
java 8

 

 

메모리에 객체를 생성하면 Eden에 저장이 됩니다. 그리고 Eden 영역이 가득 차면, S1 또는 S2로 이동되고, 둘 중에 하나가 가득 차면 비어있는 곳으로 이동하게 됩니다. 그렇기 때문에 S1 또는 S2 둘 중에 하나로 무조건 비어있는 상태가 되어야 합니다. 이러한 메커니즘 때문에 Minor GC가 발생됩니다. Minor GC는 간단히 설명하면 Eden 또는 S1, S2에서 사용하지 않는 객체들을 삭제합니다. 

 

여기서 사용하지 않다는 개념은 개발자가 판단하지 않습니다. 시스템에 어떤 영향을 끼칠지 모르기 때문입니다.

 

S1S2에서 오랫동안 살아남은 객체는 Old 영역으로 이동하게 됩니다. 만약 Old 영역이 가득 차면 Major GC가 발생합니다. 

 

각 객체는 Minor GC에서 살아남은 횟수를 기록하는 age bit를 가지고 있으며, Minor GC가 발생할 때마다 age bit 값을 1씩 증가하게 되며, age bit 값이 MaxTenringThreshold라는 설정 값을 초과하게 되는 경우 Old Generation 영역을 객체가 이동됩니다. 

 

G1 컬렉터는 Young 영역과 Old 영역을 함께 관리하며, Full GC 시에는 Old 영역의 가비지 컬렉션을 수행합니다. G1의 특징 중 하나는 전체 힙을 몇 개의 영역(Region)으로 나누고, 이를 사용하여 가비지 컬렉션을 진행한다는 점입니다. 

 

1. 현재 실행 중인 스레드가 멈추고, 루트 객체에서 출발하여 Old 영역에 있는 모든 살아있는 객체들을 표시합니다.
2. 초기 표시 단계 이후에는 스레드가 다시 실행되며, 동시에 Old 영역의 객체를 표시합니다. 이 과정에서 애플리케이션은 계속 실행됩니다.
3. 2번 단계 중에 변경된 객체들을 확인하고, 추가로 표시할 객체를 찾습니다. 이 과정은 스레드가 멈추고 수행됩니다.
4. 가비지 컬렉션으로 인해 발생한 빈 공간을 식별하고, 그 공간을 재사용하기 위해 객체를 이동시킵니다. 

 

permanent 영역은 힙 영역에 존재했습니다. 클래스 내부의 메타 데이터를 저장하는 영역이며, 클래스, 메서드 등을 관리했습니다. 하지만 자바 8 이후부터 metaspace 영역으로 대체되면서, 스태틱 객체는 힙 영역으로 편입하게 되었고, GC의 대상이 되었습니다. 그렇다면 스태틱 객체는 왜 힙 영역으로 이동하게 되었을까요?

 

static StaticDto s = new ArrayList<StaticDto>();
...
s.add(new StaticDto());
s.add(new StaticDto());
s.add(new StaticDto());
s.add(new StaticDto());

 

 

위의 코드처럼 collection 형태로 스태틱 객체를 생성하고, 스태틱 객체에 add 연산이 이루어지면 어떻게 될까요?

permenent 영역의 메모리가 부족하게 되어서 OOM [OutOfMemory]이 발생하게 됩니다. 실제로 permenet 영역에 메모리 부족 문제는 종종 발생하던 이슈였다고 합니다.