어느날 Heap Memory가 1초만에 가득 차버렸다
이건 또 무슨일인가
테스트 중 갑자기 무한로딩이 걸리더니 아무런 반응이 없어졌다.
로그를 봤지만 에러는 떨어지지 않았고 엄청 천천히 처리되고 있을때 메모리가 가득찬걸 확인할 수 있었다.
여러 사용자가 동시에 테스트를 진행하고 있다보니 누가 어떤 프로세스로 인해 원인을 파악하기 힘든 상태였다.
확실한건 힙 메모리가 순식간에 가득 찼다는건 용량이 큰 작업을 실행했다는 것
에러 내용을 보면 MemoryError
와 GC
가 가장 눈에 띄었다
java.lang.OutOfMemoryError: GC overhead limit exceeded
2개의 역할이 무엇인지 부터 파악해보자
stack
byte, short, int, long, boolean, char 타입의 데이터들이 할당되어 데이터가 저장된다.
stack은 OS에 의해 관리가 된다.
heap
Integer, String, ArrayList, Object 타입의 데이터들이 올라간다.
프로그램에 의해 관리
메모리에 데이터가 올라가면 처음에는 Eden
영역으로 생성이 됩니다.
이후 GC
에따라 Surivor1,2
로 이동 후 최종으로 Old
에 저장되게 됩니다.
사용 빈도에 따른 참조 여부에 따라 Survivor1,2
로 이동후 Old
로 이동하게 됩니다.
GC
는 garbage collection의 단축어로 메모리에 더 이상 사용하지 않는 정보를 정리하는 역할이다.
Heap Memory
에 더 이상 사용하지 않아 낭비되는 메모리를 정리해주는 것
Minor GC는 Eden, Survivor1,2
메모리를 정리합니다.
Eden
영역 메모리가 가득차게 되면 Minor GC가 동작합니다.
Eden
-> Survivor1
또는 Survivor2
로 이동Survivor1
<-> Survivor2
데이터 이동Survivor1,2
동시에 적재 되지 않고 Survivor1
-> Survivor2
로 이동할때 모든 데이터를 넘겨주게 됩니다. (둘 중 하나만 데이터가 적재)GC
실행마다 Survivor1,2
안에서 데이터를 이동시키며 정리Survivor1,2
에서 오래된 데이터를 Old
로 이동Full GC는 Old
메모리를 정리합니다.
Old
영역 메모리가 가득차면 Full GC가 동작합니다.
동작 시, 어플리케이션이 멈추게 됩니다 (Stop the world)
그러면 서버에서 GC
가 처리를 지켜보자
jstat -gcutil PID 1000 (1000은 1초에 한번씩 Update)
S0, S1, E, O
각각 위에서 설명 한 Heap 메모리의 앞 자리만 표시
중간쯤 Minor GC가 실행되어 데이터가 정리되면서 Survivor
영역이 옮겨진게 확인이 됩니다.
다시 돌아와서 OutOfMemoryError 는 Heap
공간이 부족해서 발생으로 추측이된다.
그렇다면 Heap
공간이 왜 부족했을까
거기에 대한 해답은 GC overhead limit exceeded 에서 파악이 가능했다.
위 에러가 발생한 경우는 GC 수행에 98% 이상을 소비, Heap 메모리가 2% 미만 복구
정리할 데이터가 없이 모두 사용중이라는 것
그렇다면 어디서 많은 메모리를 잡고 있을까
업로드 된 Excel를 읽고 데이터를 가공 중 문제가 발생한 것
while (rowIndex < worksheet.getPhysicalNumberOfRows()) {
--생략--
if (row != null && row.getCell(0) != null) {
rowIndex++;
--생략--
업로드된 Excel A열의 데이터만 필요하여, A열만 데이터를 가져오도록 되어있다.
while의 탈출 조건을 보면 worksheet.getPhysicalNumberOfRows()
보다 값이 커야한다
아래의 엑셀을 업로드 시 getPhysicalNumberOfRows()
값은 11이 됩니다
하지만 A의 열의 값이 있어야지 rowIndex
값을 올릴 수 있다.
무한루프에 빠져 heap
메모리가 부족해졌다
테스트 서버여서 파악하기 쉬웠지만, 이용량이 많은 사이트였다면 원인 파악하기 쥐약일 것 같다.