Full GC가 제대로 되지 않는다면?!
17년 9월 드디어 JDK9이 release되었습니다. 14년 3월에 JDK8이 출시된 후로 무려 3년6개월만입니다. 작년 Java One에서 Jigsaw프로젝트 리뷰하면서 올 연초에 나온다고 했었는데...계속 연기되었었죠 ^^;
뭐 어찌됐건 JDK9이 release된 마당에 아직도 JDK7을 쓰고 있다고 자책하면서 JDK8으로 업그레이드를 했다면, 그리고 여러분이 운영하는 시스템이 퍼블릭 망에서 어느 정도의 트래픽을 받고 있다면...여러분은 수일...아니 수 시간만에 심각한 장애를 경험하게 되실겁니다.
'왜죠? build시에도 문제가 없었고, runtime시에도 잘 기동되어
서비스가 잘 되고 있는데요?'
그 이유를 설명하기 위해서는 무엇보다도 JDK8이전과 이후의 jvm 구조에 대한 간단한 설명이 필요합니다.
이미 알려진 바와 같이 JDK7까지 있었던 Perm 영역은 이제 역사속으로 사라졌습니다. 덕분에 얘네도 함께 ByeBye~~(-XX:PermSize, -XX:MaxPermSize...추가로 이녀석도 “java.lang.OutOfMemoryError: PermGen space” error )
그럼 Perm영역에 저장하는 놈들은 어디루??
. Methods of a class -> Native 영역으로 이동
. Names of the classes -> Native 영역으로 이동
. Constant pool information(String포함) -> Heap영역으로 이동
. Static Variable -> Heap 영역으로 이동
JDK8에서는 이런 Native영역을 Metaspace로 부르며, 별도로 사이즈 지정하지 않으면 JVM이 동적으로 알아서 조정한다고 하는데요...
근데 문제는 분명이 JVM이 동적으로 적용한다고 하던 Metaspace를 그냥 default로 하게 되면 크나큰 장애에 직면하게 될 수도 있다는 것 입니다.
심각한 성능저하! 초당 몇백건씩 처리하던 시스템이 초당 1건도 처리하지 못하는 현상 발생
실제로 저자가 운영중인 시스템과 타 계열사에 시스템이 동일한 문제로 큰 장애에 직면했었습니다. OOM도 발생하지 않고, Old영역의 상태가 100%로 계속 지속되면서 heap area에 대한 minor gc만 발생했었죠.
그러다 보니 Old영역을 사용하지 못하고 반복적으로 heap 100%와 minor gc가 발생하면서 심각한 성능저하를 초래하게 됩니다. 아래는 동일 현상에 대한 재현입니다. (발생예상원인은 하단에..)
Parallel GC에 Heap Memory를 -Xmx1G 로 설정하고 Metaspace는 지정하지 않은 체로 부하를 발생시켰을때
Parallel GC에 Heap Memory를 -Xmx1G 로 설정하고 Metaspace를 XX:MaxMetaspaceSize=256m 로 지정했을 때
그럼 MaxMetaspaceSize 설정을 하지 않은 것과 Old영역의 Full GC가 발생하지 않은 것에 어떤 연관관계가 있을까?
가장 큰 이유는 클래스의 Meta정보가 그대로 Metaspace에 유지됨에 따라 Old영역에 대한 GC조건에 성립되지 않았을 가능성이 제일 큽니다.
과거 JDK8u40미만에서 JVM옵션으로 CMSClassUnloadingEnabled 정의되어 있지 않았을 때 FullGC가 수행되지 않았던 현상에 대한 bug report(https://bugs.openjdk.java.net/browse/JDK-8049831)를 보면 아래와 같은 댓글이 있습니다.
setting -XX:MaxMetaspaceSize will cause the metadata to be freed when hitting MaxMetaspaceSize (infinite by default). An alternative workaround is to not set -XX:-CMSClassUnloadingEnabled.
즉, MaxMetaspaceSize에 도달하게되면, 클래스들에 대한 metadata를 메모리에서 release하게 되는데, 해당 설정이 없었기 때문에 metadata에 대한 release를 하지 못했고 그로 인해 적절한 Full GC가 발생하지 못했다고 예상할 수 있습니다.
현재 MaxMetaspaceSize를 정의한 후에 약 2달 이상 Application의 특별한 장애없이 잘 사용하고 있습니다. 워낙 금융솔루션들이 솔루션 같지 않아 각종 버그들 잡아주고 튜닝하느라 고생은 했지만서도요.
우리나라의 왠만한 IT운영조직들의 상당부분은 아직도 JDK7을 사용하고 있을 겁니다.(특히 금융권...금융권은 JDK6도 있을듯). 아마도 JDK업그레이드 고민하면서 JDK8에 대한 호환성 검토까지만 하고 부하테스트까지는 하지 않을 가능성이 높을 것 같은데요, 그렇다면 운영중에 헬을 경험하실 수 있을 겁니다.
아무쪼록 운영 중이던 시스템에 대한 JDK8 업그레이드 수행하신다면 반드시 아래와 같이 MaxMetaspaceSize설정을 해주시길 권장드리며 추가로 힙메모리가 엄청 크게 설정되어 있지 않다면, G1 gc설정도 나쁘지 않을 것입니다. 아래는 메모리 관련 JDK8에서의 JVM옵션입니다.
그럼 오늘도 편안히~~^^
$JAVA -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/***/***/****/log -Xmx2048m -Xms1024m -XX:MaxMetaspa ceSize=512m -XX:MetaspaceSize=256m -XX:+UseG1GC ...
추가) MaxMetaspaceSize를 적용해주지 않아서 발생하는 케이스는 현재까지 Java+groovy, Java+scala로 파악되었습니다. 실제 Java만 사용했을 때는 오라클에 가이드대로 별도의 설정이 없어도 큰 문제가 없는 것으로 판단됩니다.
추가) 실제 Java+Groovy의 경우에는 발생할 수 있는 memory leak 정리해보았으니, 아래 링크를 통해 참고해주시기 바랍니다.
https://brunch.co.kr/@heracul/9
GC에 대한 기본이해에 도움될 글 : http://itmining.tistory.com/24
참고문헌
- https://blogs.oracle.com/poonam/about-g1-garbage-collector%2c-permanent-generationand-metaspace
- https://dzone.com/articles/java-8-permgen-metaspace