Metaspace OOM

고승원·2023년 9월 13일
0

Java

목록 보기
2/3

문제 상황

점심시간 전 jenkin를 통해 빌드와 서버 접속이 잘 되는것을 확인하고 점심을 먹으러 갔다.

점심을 먹고 쉬고 있는데 갑자기 서버 접속이 안된다고 얘기를 들었었다.

일단 해결한 뒤 문제를 파악해 봤다.

배경

현재 배포 플로우는 다음과 같다.

S3, Docker Hub를 이용하는 보편적인 CI/CD 플로우와 다르다.
외장 톰캣을 사용하기 때문에 tomcat의 autoDeploy 속성을 십분 활용하고 있다.
autoDeploy 속성은 Root.war 파일이 변경감지 후 배포하는 방법인데, 무중단 배포를 지원하지 않는다.
자세한 내용은 여기를 보면 된다.

원인

자료

제일 먼저 jenkin의 빌드 로그를 열어봤다. 분명 모두 성공한 것을 확인했고, 서버 접속도 되는걸 확인했다.
여기는 문제가 없다고 판단했고, 다음으로 해당하는 서버 로그를 찾아보았다.

	java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Metaspace
		at java.util.concurrent.FutureTask.report(FutureTask.java:122)
		at java.util.concurrent.FutureTask.get(FutureTask.java:192)
		at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:820)
		at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:474)
		at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1659)
		at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:314)
		at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
		at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1170)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1396)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1400)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1368)
		at java.lang.Thread.run(Thread.java:748)
	Caused by: java.lang.OutOfMemoryError: Metaspace

해석

Metaspace에 OOM이 발생했다. Metaspace란?

Metaspace는 JVM java 8 PermGen 영역 대신 생긴 영역이다. 이 영역에는 Classloader가 로드한 클래스의 meta data가 저장되어 있다.
이전 버전과 다르게 Native Memory 영역에 속하기 때문에 java Heap이 아닌 운영 시스템에서 사이즈를 관리하게 된다. 이 사이즈 조절은 기존의 PermGen 보다 더욱 신중해야하는데, 만약 Metaspace가 OOM이 발생하면 애플리케이션이 아니라 서버 전체를 다운시킬 수 있다.

배포할 때 빌드중인지 모르고 빌드를 한번 더 눌렀는데 이게 문제였다.

  1. 젠킨스를 통해 war파일을 덮어씌운다.
  2. 변경감지로 인해 빌드가 되어 클래스 로더에 의해 Metaspace에 meta data가 적재되고있다.
  3. 젠킨스를 통해 두번째 war파일이 덮어씌워진다.
  4. 2번 빌드가 완료되기 전 다른 빌드가 시작되어 Metaspace에 OOM이 발생했다.

그렇다면 서버 접속은 잘되었을까?
젠킨스에선 war 파일을 정상적으로 던졌기 떄문에 빌드를 성공했고,
서버는 아직 빌드중이기 때문에 접속을 해도 기존에 배포중이던 서비스로 접속이 되었다.
이후 빌드가 되가면서 OOM 발생

결과

현재 가동중인 서버의 JVM 메모리 할당/사용량을 보자.

1323M / 1332M 로 굉장히 타이트하게 사용되고 있음을 알 수 있는데, OOM이 또 날까 두려웠다.

JVM 옵션을 수정해 Metaspace에 대한 영역을 늘려주자.

오잉? 왜 이렇게 되어있는지 모르겠다. Metaspace는 클래스 로딩/언로딩 동안 동적으로 크기가 조절된다. 관련해서 두가지 속성이 있다.

  • -XX:MetaspaceSize : Metaspace의 초기 크기
  • -XX:MaxMetaspaceSize : Metaspace의 최대 크기

왜 초기 크기보다 최대 크기가 더 작은지 모르겠지만, 초기 크기는 처음 그대로, 최대 크기는 옵션값을 지웠다.

이렇게 선택한 근거는 다음과 같다.

  • Metaspace의 값을 너무 크거나, 작게 만들게 되면 메모리가 낭비되거나, OOM이 터지게 된다. 메모리가 낭비되는 것도 싫지만, OOM은 서버 자체가 죽어버리기 때문에 정말 위험하다.
  • JVM이 동적으로 Metaspace의 크기를 늘렸다 줄였다 하기 때문에 최대 크기를 정하지 않아도 된다 생각했다.

결론

빌드를 확인하기보다 실제 서버가 올라간 로그를 확인해보자.
Metaspace OOM이란게 있고, 왜 생기는지 알게 되었다.
JVM 속성을 상황에 맞게 튜닝해서 사용하자.

profile
봄은 영어로 스프링

0개의 댓글