-
Notifications
You must be signed in to change notification settings - Fork 0
[트러블슈팅] Github Actions SonarCloud
Jacoco와 SonarCloud를 연동한 뒤, 첫 기능 개발 PR에서 아래와 같은 이슈가 발생했습니다.
로그에서 알 수 있듯이 build에서 실패하여 해당 job이 수행되지 않았습니다.
build에서 실패한 이유는 프로젝트에서 설정한 라인 커버리지를 충족하지 못했기 때문입니다. 커버리지를 충족하지 못한 클래스들은 DTO나 분기 처리에 따른 멤버 함수를 호출하기 때문에 라인 커버리지 충족이 힘든 케이스였습니다. 이에 해당 클래스들을 커버리지 측정에서 제외하는 처리를 하였습니다.
위의 사진과 같이 코드를 수정한 뒤, push를 하고 CI를 수행했습니다.
결과는 CI 워크플로우는 정상적으로 PASS 했지만, Sonar 플로우에서는 FAIL 했습니다.
위의 사진과 같이 Sonar 워크 플로우에서 JVM Metaspace 관련 오류가 발생했습니다. 처음 마주하는 오류여서 그 원인을 바로 파악하기가 힘들었습니다.
추가로 위의 사진과 같은 로그가 남겨져 있어, 메모리 관련 이슈임을 인지하고 구글링을 하였습니다.
여기를 보면, 스택오버플로우에 저와 비슷한 이슈를 마주한 케이스가 많았습니다.
java.lang.OutofMemoryError: Metasapce
이 오류의 원인은 자바 클래스 메타데이터는 메타 공간에 할당되는데, 클래스 메타데이터가 할당될 메타공간이 소모되면 해당 오류가 발생합니다. 자바 클래스의 메타데이터가 할당될 공간은 MaxMetaSpaceSize 매개변수로 제한됩니다.
이 오류를 해결하기 위해서는 MaxMetaSpaceSize 값을 늘려줍니다. 단, 자바 힙 공간에 여유가 있을 경우에 행해야 하는 조치입니다.
여기의 정리된 내용을 참고하였습니다.
여기서부터는 프로젝트에서 해당 이슈를 해결하기 위해 수행한 처리를 정리하겠습니다.
- gradle.properties 파일에서 maxheap 사이즈 늘리기
위의 코드 3줄을 해당 파일에 추가하였습니다.
- CI-Sonar 워크플로우에 clean 수행
위의 보이는 코드는 CI 워크플로우의 일부입니다. 기존에 gradle을 캐싱하던 부분에서 메모리 이슈가 발생했기 때문에, 캐싱 처리를 주석 처리하고 clean 후, 실행 중인 프로세스에 대해 stop으로 종료시켜 주었습니다.
gradle은 gradle demon을 사용해 빌드의 성능을 높이기 위해 메모리에 캐싱합니다. 캐싱 때문에 오히려 메모리가 초과되어 stop 명령어를 통해 demon을 정지시켰습니다.
- CI 워크플로우 하나만 동작
기존에 CI 워크플로우에서 sonarcloud를 적용한 것이 아닌, 새로운 워크플로우를 작성하여 변경점이 해당 파일에서만 작용하도록 하였습니다. 그 이유는 도입 과정에서 기존에 정상적으로 수행되는 워크플로우를 변경하기 보다 새로 작성하고 이슈 발생 시, 삭제하려고 했기 때문입니다.
깃헙 액션에서 두 개의 CI를 수행하면서 속도와 처리 측면에서 효율적이지 못한 것인가 싶어 기존 CI 플로우가 동작하지 않도록 처리하였습니다.
결론.
위의 방법을 순차적으로 모두 적용하였더니 이슈가 해결되었습니다. 복합적으로 얽힌 이슈였기 때문에 특정한 포인트가 원이이라고 단정할 수 없었습니다..
Java 8부터 JVM의 메모리 영역 중 Permanent Generation 메모리 영역이 사라지고 Metaspace 영역이 생겼습니다. 이 영역은 Java의 Classloader가 로드한 클래스들의 메타 데이터가 저장되는 공간입니다.
앞선 버전과 큰 차이는 metaspace가 java 7의 permgen과 달리 Heap 영역이 아닌 Native Memory 영역에 위치한다는 점입니다.
이렇게 변경됨에 따라 어플리케이션이 많은 클래스를 로드한다면 단순히 어플리케이션의 OOM이 아니라 전체 서버를 다운시킬 수도 있게 되었습니다. 따라서 적절한 flag 값(-XX:MaxMetaspaceSize 등)을 설정해주는 것이 필요할 수 있습니다.
또한, Heap Size에 대한 모니터링 뿐만 아니라 Metaspace에 대한 모니터링이 필요할 수 있습니다. 리눅스 명령어 top 을 통해 프로세스 사이즈를 체크하는 방법으로 간단히 확인할 수 있습니다.
그렇다면, 클래스의 메타데이터는 무엇을 말하는 것일까? JVM이 클래스에 대해 알아야 하는 모든 정보를 의미합니다. 여기에는 Class의 구조, Method Metadata, constant pool, annotation이 있습니다.
예를 들면, java.lang.Class는 자바의 heap 메모리 안에 있는 object입니다. 하지만, 이 class의 metadata는 자바에서의 object가 아니기에 heap 메모리 안에 위치하지 않습니다. heap 메모리의 외부인 native memory에 위치하며 이곳이 바로 metaspace 입니다.
Metaspace는 언제 할당되고 릴리즈 될까?
Class가 로드되고 런타임이 JVM에 준비될 때, Class의 meatadata 저장을 위해 metaspace가 클래스로더에 의해 할당 됩니다. 그리고 클래스 로더가 unload
될때에만 릴리즈가 됩니다.
즉, 릴리즈는 어떤 살아있는 인스턴스도 없고 어떤 참조도 없는 상태에서 GC가 일어난 이후에 진행됩니다.
Metaspace의 사이즈를 조정하려면?
대표적인 파라미터로 MaxMetaspaceSize와 CompressedClassSpaceSize가 있습니다.
-XX:MetaspaceSize
초기/최소 사이즈 지정
-XX:MaxMetaspaceSize
최대 사이즈 지정
-XX:CompressedClassSpaceSize
compressed Class size의 가상 사이즈를 지정할 수 있다. ( 기본 1g이며 최대 3g까지 가능하다. )
여기 참고 하였습니다.