1. GC라는 것은 무엇인가?

GC(Garbage Collection)는 Java에서 더 이상 사용되지 않는 객체를 정리해주는 메모리 관리 기능이다. 객체 그림 가능성을 고려하지 않고, 객체가 더 이상 참조되지 않는 경우 자동으로 해제한다.

대표적인 GC 알고리즘:

  • Serial / Parallel GC: Throughput 중심
  • CMS: 응답 시간 최소화
  • G1GC: Region 기능을 활용해 큰 heap에서 효율가 높은 GC

2. 실전 사례: 무한 루프 → Full GC 연속 발생 → WAS 달성

현재 Java 여보 앱에서 다음과 같은 증상이 발생했다:

  • 일정 시점 이후 CPU 100% 고정
  • GC 로그에 Full GC가 발생하고 다음과 같은 메시지 표시: Pause Full (System.gc())
  • 결과적으로 WAS가 응답 불가 상태에 빠짐

또한, G1GC 로그에서는 다음과 같은 심각한 메시지가 확인되었다:

  • Evacuation Failure: Young GC에서 객체를 이동시킬 공간이 부족
  • to-space exhausted: 힙 내에 여유 공간이 없어 객체 복사가 불가능
  • Full GC (Allocation Failure): GC가 메모리를 확보하지 못해 강제로 Full GC 발생

GC 후 사용량이 used=6283241Kused=6283241K로 변화 없이, 실질적으로 정리되지 못한 객체가 그대로 유지됨을 보여준다. 이는 힙이 포화 상태에 도달한 후에도 메모리를 회수할 수 없는 상황이었다.


3. Heap Dump 분석을 통한 문제 파악

Heap Dump 수집:

jmap -dump:format=b,file=heapdump.hprof <PID>

분석 도구: Eclipse MAT (Memory Analyzer Tool)

분석 결과:

  • 특정 VO 클래스가 List에 계속 객체를 add()하고 size를 키우는 구조 구조
  • 참조가 끊기지 않아 GC 대상이 되지 않고 메모리 문제가 발생

예시 코드:

for(int i = 0; i < list.size() ; i++){
	list.add(AAA);
    //기타 문서 처리
}

특정 시점에 진입하는 트랜잭션 일부가 이 루프에 빠져 종료되지 않음으로써, Full GC가 연속적으로 발생했고 최종적으로 WAS 다운까지 이어졌다.


4. 조치 및 확인

  1. 무한 루프에 종료 조건 추가
  2. 해당 트랜잭션을 단일 환경에서 재현 후 테스트
  3. GC 로그 확인을 위한 JVM 옵션 추가:
-XX:+PrintGCDetails -Xloggc:/var/log/gc.log

Heap 크기 일시 증설 (-Xmx10g)


5. 결론 및 교훈

  • Full GC는 결과일 뿐, 그 원인은 따로 있다.
  • Heap Dump는 실시간 분석에서 중요한 도구이다.

최근 시스템에서 PDF 파일을 업로드/삭제하는 과정에서 "received failure with description 'Failure'" 오류가 발생하는 문제가 있었다. 이 문제를 해결하기 위해 원인을 분석하고 조치를 취한 결과, 파일을 열고 닫는 처리에서 발생한 문제임을 확인했다. 본 포스팅에서는 이 오류의 원인과 해결 방법을 정리하여 공유하고자 한다.


 

오류 발생 상황

  • 특정 PDF 파일들이 정상적으로 업로드/삭제되지 않고 오류 메시지를 출력함
  • 동일한 파일을 다른 환경에서 업로드하면 문제가 발생하지 않음
  • 시스템 내에서 해당 파일이 사용 중인 상태로 감지됨

문제 원인 분석

  1. 파일 점유 문제: 특정 프로세스가 파일을 점유하고 있어 업로드 실패 발생
  2. Java 파일 핸들링 문제: 파일을 열어둔 후 닫지 않는 코드로 인해 리소스가 반환되지 않음
  3. 파일명 문제: 특수 문자 또는 공백이 포함된 파일명이 시스템에서 인식되지 않는 문제 발생 가능성

해결 과정

  1. lsof 명령어를 사용하여 파일을 점유한 프로세스 확인
    • lsof | grep <파일명> 명령어 실행 후, 파일을 점유 중인 프로세스를 찾음
    • 특정 프로세스에서 해당 파일이 열려 있는 상태를 확인
  2. Java 코드에서 파일 닫기 누락 확인
    • Java 코드 내에서 FileInputStream, BufferedReader, FileWriter 등의 객체를 생성한 후 close()를 호출하지 않아 리소스가 반환되지 않음을 확인
    • 문제 코드 예시:
FileInputStream fis = new FileInputStream("example.pdf");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis)); // 파일을 읽는 로직
// reader.close(); (이 부분이 누락됨)

 

  1. 코드 수정 및 테스트 진행
    • try-with-resources 구문을 사용하여 자동으로 파일을 닫도록 변경
    • 수정된 코드 예시:
try {
    FileInputStream fis = new FileInputStream("example.pdf");
    BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
} catch (IOException e) {
    e.printStackTrace();
}
  • 위 방식으로 코드 수정 후 파일 점유 문제가 해결됨

 


결론

이번 오류는 파일을 열고 닫지 않는 코드로 인해 발생한 문제였다.

lsof 명령어를 활용해 파일을 점유한 프로세스를 찾고,

Java 코드에서 close()를 호출하도록 수정하는 방법으로 해결할 수 있었다.

+ Recent posts