자바에서 메모리는 자동 가비지 수집기(Garbage Collector, GC)를 통해 관리됩니다. JVM은 메모리 내의 객체를 스스로 추적합니다: 만약 객체에 대한 참조가 더 이상 없다면, 해당 객체는 도달 불가능하게 되며 삭제될 수 있습니다.
GC는 Mark, Sweep 및 때때로 Compact와 같은 여러 단계로 구성됩니다. Mark 단계에서 GC 루트에서 접근 가능한 객체들은 살아있는 것으로 표시됩니다. 그 후 Sweep이 시작됩니다: 사용되지 않는 객체가 삭제됩니다. 가끔 Compact이 수행되어 메모리 단편화를 방지합니다.
다양한 GC 유형이 있습니다: Serial, Parallel, CMS, G1. 이는 애플리케이션의 부하 유형에 따라 선택해야 합니다.
List<byte[]> dataList = new ArrayList<>(); while(true) { dataList.add(new byte[1024*1024]); // 객체는 여전히 도달 가능합니다 }
이 예시에서 객체는 결코 도달 불가능해지지 않으며, GC는 이를 삭제하지 않아서 OutOfMemoryError가 발생합니다.
객체에 더 이상 참조가 없는 경우, 그의 최종자(finalizer)(finalize())가 호출되는 것은 언제 보장되나요?
답변: finalize() 메서드 호출은 전혀 보장되지 않습니다! 이 메서드는 한 번도 호출되지 않거나 지연되어 호출될 수 있습니다. 자원 해제를 위해 finalize()에 의존할 수 없습니다.
@Override protected void finalize() throws Throwable { // 작동하지 않을 수 있습니다! }
이야기
대형 전자상거래 시스템에서 개발자들은
finalize()메서드를 이용해 임시 파일 정리를 자동화하려고 했으나, 이 메서드의 드문 호출로 인해 캐시가 임시 파일로 가득 차서 디스크 부족과 다운타임이 발생했습니다.
이야기
고부하 REST API에서 큰 객체에 대한 내부 참조 캐시가 실수로 저장되어 메모리 해제를 방해했습니다. 애플리케이션이 OutOfMemoryError로 종료된 후 분석 결과, 캐싱 메서드의 오류가 밝혀졌습니다.
이야기
최종자는 네트워크 연결 해제를 위해 사용되었으며, 이를 "신뢰할 수 있는 방법"으로 간주했습니다. 이로 인해 연결이 시스템 내에서 불확실하게 오랫동안 남아 있어 잠금 및 연결 풀 고갈을 초래했습니다.