본문 바로가기

일상

메모리가 부족해서 힙을 늘렸더니 아예 안 뜰 때 (32비트 JVM의 4GB 벽)

반응형

톰캣 힙을 4GB로 올렸는데 기동조차 되지 않는 경우가 있습니다. 왜 그런지, 내 환경이 여기 해당되는지 확인하는 방법을 알아보는 글입니다.


1. 증상 - 늘렸더니 오히려 안 뜬다


메모리 부족이 반복되면 가장 먼저 손대는 게 힙 설정인데, 값을 올리고 재기동했더니 톰캣이 아예 올라오지 않는 경우가 있습니다.



여기서 헷갈리기 쉬운 게, 이건 OOM이 아니라는 점입니다. 애플리케이션이 메모리를 다 써서 죽은 게 아니라 JVM 자체가 시작을 거부한 것이라, 로그에는 스택 트레이스도 남지 않고 그냥 프로세스가 끝납니다.


메시지도 그대로 말해 주고 있습니다. 요청한 크기가 표현할 수 있는 최대치를 넘었다는 뜻이라, 메모리가 모자란 게 아니라 그만큼 큰 숫자를 애초에 받을 수 없는 상태입니다.


2. 원인 - 4GB는 힙 혼자 쓰는 게 아니다


32비트 프로세스는 포인터가 32비트라 주소를 매길 수 있는 공간이 4GB로 고정되어 있고, 이건 설정으로 늘릴 수 있는 값이 아닙니다.


문제는 그 4GB를 힙이 독차지하지 못한다는 데 있습니다. 같은 공간을 아래 것들이 나눠 씁니다.


- OS가 예약해 가는 몫 (커널 영역)


- 네이티브 힙 - JNI, 드라이버, 압축 라이브러리 등이 쓰는 자바 밖 메모리


- 스레드 스택 - 스레드 하나당 512KB에서 1MB씩, 톰캣이라면 수백 개


- 코드 캐시와 메타스페이스 - 자바 8부터 퍼머젠이 메타스페이스로 바뀌었지만, 32비트에서는 이것도 결국 같은 4GB를 갉아먹습니다


게다가 힙은 연속된 공간을 한 번에 잡아야 해서, 조각이 나 있으면 남은 총량이 충분해도 확보에 실패합니다. 그래서 실제로 줄 수 있는 값은 4GB에서 한참 내려갑니다.


3. 내 JVM이 몇 비트인지부터 확인한다


의외로 이걸 모르고 지나가는 경우가 많은데, 확인은 명령 한 줄이면 끝납니다.



java -version 출력에 64-Bit Server VM 이 없으면 32비트라고 보시면 되고, 더 확실하게는 sun.arch.data.model 값이 32 인지 보면 됩니다.


운영체제가 64비트라고 해서 JVM도 64비트인 건 아닙니다. 오래된 서버에는 64비트 OS 위에 32비트 JDK가 그대로 남아 있는 경우가 흔한데, 처음 설치할 때의 선택이 그대로 굳은 것입니다.


4. 그러면 얼마까지 줄 수 있나


환경마다 다르지만 대략 이 정도가 현실적인 상한입니다.


- 32비트 리눅스 : 2.5GB에서 3GB 부근


- 32비트 윈도우 : 1.5GB 부근 (사용자 영역이 2GB로 잘려 있어 더 낮습니다)


숫자를 외우실 필요는 없고, 2GB를 넘기려 할 때부터 실패 가능성을 의심하시면 됩니다. 그리고 이건 값을 잘 고르면 통과하는 문제가 아니라, 구조상 그 위로는 못 가는 문제입니다.


5. 해법은 64비트 전환뿐이다


32비트에서 힙을 더 늘리는 우회로는 없습니다. 값을 조정해 기동을 통과시킬 수는 있어도 원래 필요했던 만큼을 주는 건 불가능하기 때문에, 결국 64비트 JDK로 갈아타는 것이 유일한 방향입니다.


다만 그냥 갈아 끼우면 끝나는 작업은 아니라서, 아래를 같이 봐야 합니다.


- 네이티브 라이브러리 - JNI로 부르는 .so나 .dll이 32비트면 같이 64비트로 교체해야 하고, 안 그러면 로딩 시점에 실패합니다


- JDBC 드라이버 - 순수 자바 드라이버는 상관없지만, 네이티브에 의존하는 방식이라면 클라이언트 쪽도 64비트여야 합니다


- 힙 재산정 - 포인터가 커져 같은 객체라도 메모리를 더 쓰는데, 32GB 미만 힙에서는 압축 포인터가 기본으로 켜져 이 증가분이 대부분 상쇄됩니다



당장 전환이 어렵다면 힙을 2GB 아래로 두고 버티되, 그건 시간을 버는 것이지 해결이 아닙니다. 그 사이에 OOM의 진짜 원인이 누수인지 아니면 한 번에 너무 많은 데이터를 올리는 처리인지를 따로 밝혀 두는 편이 낫습니다. 원인이 누수라면 힙을 8GB로 늘려도 터지는 시점만 뒤로 밀릴 뿐입니다.


6. 정리


- 힙을 올렸는데 기동이 실패하면 OOM이 아니라 JVM이 시작을 거부한 것이다


- 32비트는 주소공간 4GB가 고정이고, 힙은 그중 일부만 쓴다


- 실제 상한은 리눅스 2.5-3GB, 윈도우 1.5GB 부근


- 64비트 JDK 교체가 유일한 해법이며, 네이티브 라이브러리도 같이 봐야 한다


- 전환 전까지는 힙을 2GB 아래로 두고 OOM의 진짜 원인을 따로 잡는다


메모리가 모자라 보이면 값을 올리는 게 자연스러운 반응이지만, 올릴 수 없는 구조라면 방향 자체가 달라집니다. 값이 거부당했을 때 설정을 더 만지기 전에 실행 환경이 몇 비트인지부터 보시면, 그 뒤로 할 일이 훨씬 명확해집니다.


함께 보면 좋은 글:
- 커넥션 풀(DBCP) 경고가 갑자기 쏟아질 때 (장애가 난 걸까, 이제야 보이는 걸까) - 같은 전환 작업에서 나온 커넥션 풀 편
- 톰캣(Tomcat) 6에서 9로 올릴 때 만나는 함정 5가지 (기동 실패, 한글 전멸, 400 에러) - 같은 톰캣 이관 편
- 레거시 자바 JDK 1.6에서 1.8로 이관할 때 라이브러리를 정말 다 갈아야 할까 (한 번에 변수 하나만 검증) - 같은 자바 런타임 이관 편


여기까지 32비트 JVM에서 힙을 늘리지 못하는 이유에 대해서 작성해봤습니다. 여기까지 읽어주셔서 감사합니다!

반응형