Java Compressed Class Space
JVM 의 메모리 구조는
- static (지역변수, 매개변수, 참조변수 등)
- heap (런타임에 동적으로 할당되는 객체들 map, 배열, 객체 인스턴스 등)
- metaspace (class 의 메타정보들)
크게 세 영역으로 구분하고 metaspace 에 속하는 compressed class space 라는 놈이 존재.
metaspace 는 클래스 런타임 정보들을 보관하는 공간으로 method meta, runtime annotation 등 클래스의 런타임 관련 정보들을 담고
compressed class space 는 클래스 메타정보를 보관.
따라서 한꺼번에 많은 수의 class 를 로딩할 때 부하가 가는 곳은 일반적으로 compressed class space (클래스의 메타 압축정보를 저장하기 때문)
그리고 이 compressed class space 는 jvm 기동 시 따로 값을 지정하지 않았으면 max 값이 환경에 따라 설정되는데 (metaspace 처럼 무한정 확장이 아님)
- 64bit JVM에서 CSS 기본 크기는 대략 1GB 이하로 설정
- 32bit JVM에서는 더 작은 기본값이 설정되며, 보통 128MB ~ 256MB 정도
실제로 직접 확인해봤을때
---
WAS 에서 application jar 당 많은 수의 서비스를 가지고 있는 경우,
application loading 동작에서 jar 내부의 각 class 들을 전부 loading 한 다음, 클래스의 생성자를 map 에 넣고 있음.
이 경우 발생할 수 있는 문제는
1. Compressed Class Space OOM - 많은 class 들을 로딩하고 classloader 를 close 해주지 않기 때문에
(해당 classloader 를 app classloader로 사용하고 있음, 서비스 호출 시 thread 의 context cl 을 app classloader 로 set)
2. Metaspace OOM - class 의 구조에 따라 metaspace OOM 이 발생할 수 있음.
3. Heap Memory OOM - constructor 를 map 객체에 넣는부분은 heap memory 증가로 이어짐.
---
최적화 할 수 있는 포인트는
app 로드 시 한꺼번에 클래스로딩을 전부 한 후 classloader close. 이후 다시 url classloader 만들어서 app classloader 로 set. - Compressed Class Space OOM 방지
또는 한꺼번에 jar 안의 모든 class들을 로드하지 않고 서비스 호출 시 마다 loadClass 후 생성자 캐싱.
이 경우 한꺼번에 많은 클래스를 로드하는 부하를 일으키지 않으며, 주기적으로 캐싱정보를 날릴 수 있다는 장점이 있음.
생성자 캐싱은 LinkedHashMap - LRU(Least Recently Used) 알고리즘을 사용하며 map size를 정해두고 그 크기만큼만 생성자를 저장해두는 방식. - Heap Memory OOM 방지
---
Compressed Class Space OOM 방지를 위해서는
- java process 실행 시 -XX:CompressedClassSpaceSize 로 max 값을 명시적으로 설정하거나 (그렇지 않으면 보통 1GB)
또는, UseCompressedClassPointers를 비활성화 (비활성화 시 클래스 메타데이터가 사용할 수 있는 공간의 크기가 고정되지 않음)
- 근본적으로는 주기적인 classloader.close() 호출로 공간을 확보하는 수 밖에 없음.
그러나 보통 was에서는 application 의 생명주기에 따라 app classloader 가 관리되는게 일반적.
결론
근본적으로 compressed class space oom를 막기 위해서는
주기적으로 app classloader 를 재성성 후 교체 (classloader 해제로만 해당 공간을 확보할 수 있음)
또는 운영적 관점으로는 하나의 was에 몇개 까지의 서비스 클래스를 둘 수 있을지에 대한 고민이 필요함
(내 경우 16개의 앱에 2785개의 서비스 클래스가 존재할 때, compressed class space 는 877MB 였음)
CompressedClassSpaceSize는 허용범위가 있어서 -XX:CompressedClassSpaceSize=4g로 설정할 경우 다음과 같은 메시지를 볼 것이다.
CompressedClassSpaceSize of 4294967296 is invalid; must be between 1048576 and 3221225472.
** 이미지의 문제인지 모르겠으나,
192.168.2.242:32500/pl-8-minimal-java-17:8.6
를 baseos 로 하는 WAS 이미지의 경우 -XX:CompressedClassSpaceSize=2g 가 적용되지 않음.
jps 후 jcmd <PID> VM.flags 로 확인