[Java] 자바 메타스페이스(Metaspace)에 대해 알아보자.
메타스페이스(Metaspace)
Permanent Generation to Metaspace
Java8부터 JVM의 메모리 영역 중 Permanent Generation 메모리 영역이 사라지고 Metaspace 영역이 생겼다.
Metaspace는 간단히 말해 Java의 Classloader가 로드한 class들의 metadata가 저장되는 공간이다.
JVM7까지의 Permgen(Permanent Generation)과 JVM8 이후 Metaspace의 가장 큰 차이는 Metaspace는 Permgen과 달리 Heap 영역이 아닌 Native Memory 영역에 위치한다는 점이다.
아래 그림을 참고하자.
따라서 “java.lang.OutOfMemoryError: PermGen space”과 같은 종류의 OOM은 더 이상 마주칠 일이 없으며, -Xmx option에 의해 설정되는 Heap사이즈가 아닌, Host 운영시스템에 의해서 그 사이즈가 제약된다.
그런데 그 말은 즉, 만약 애플리케이션이 많은 클래스(+ String Interning)를 로드한다면 단순히 애플리케이션의 OOM이 아니라 전체 서버를 다운시킬 수도 있다는 이야기가 될 수도 있다. 따라서 적절한 flag값(-XX:MaxMetaspaceSize 등)을 설정해주는 것이 필요할 수 있다.
또한, Heap size 모니터링 뿐만 아니라 Metaspace에 대한 모니터링이 필요할 수 있다. 가장 간단한 방법은 linux top 명령어를 통해 process size 등을 체크해보는 건데, 아래에서 모니터링에 대해 조금 더 알아보자.
참고로 Metaspace는 아래와 같은 용어들과 혼용되어 쓰이기도 한다:
Native Area, Native Memory, Off-Heap, Non-heap, Direct Memory 등
Metadata
Class의 metadata는, JVM process 안에 있는 Java class들의 runtime으로, 간단히 말해 JVM이 해당 class에 알아야하는 모든 정보(JVM class file format)를 포함한다.
JVM class file format에 포함된 정보
- “Klass” structure
- Method metadata: class file내의 method_info, bytecode, exception table, constants 등
- constant pool
- Annotations
Metadata의 예를 들면, java.lang.Class는 Java heap안에 사는 object지만, 그 class metadata는 Java object가 아니며 heap 안에 위치하지 않는다. 이 metadata는 heap 외부의 native memory 구역에 위치하며, 바로 이 구역이 Metaspace이다.
Metaspace allocation and release
Metaspace는 언제 할당되고 릴리즈될까?
할당은, Class가 로드되고 런타임이 JVM에 준비될 때 class의 metadata 저장을 위해 metaspace가 class loader에 의해 할당된다.
릴리즈는 class loader가 unload될 때만 일어난다.
아래 그림을 한번 보자.
즉, 릴리즈는 어떤 live instance도 없고 어떤 참조도 없는 상태에서 GC가 일어난 이후에야 릴리즈 되는 것이다.
MaxMetaspaceSize, CompressedClassSpaceSize
Metaspace의 사이즈를 지정하는 파라미터들 중 대표적으로 MaxMetaspaceSize, CompressedClassSpaceSize가 있다.
- -XX:MetaspaceSize : 초기/최소 metaspace size
- -XX:MaxMetaspaceSize : 최대 metaspace size
- -XX:CompressedClassSpaceSize : metaspace의 중요한 부분인 Compressed Class Size의 가상 사이즈를 지정한다. default 값은 1G이며 최대값은 3G다.
Metaspace 관련 튜닝이 가능한 전체 파라미터들을 조회해보자. 아래 커맨드를 활용할 수 있다.
java -XX:+PrintFlagsFinal -version | grep <concept>
결과 예시를 한번 보면:
> java -XX:+PrintFlagsFinal -version | grep Metaspaceuintx InitialBootClassLoaderMetaspaceSize = 4194304 {product}
uintx MaxMetaspaceExpansion = 5451776 {product}
uintx MaxMetaspaceFreeRatio = 70 {product}
uintx MaxMetaspaceSize = 18446744073709547520 {product}
uintx MetaspaceSize = 21807104 {pd product}
uintx MinMetaspaceExpansion = 339968 {product}
uintx MinMetaspaceFreeRatio = 40 {product}
bool UseLargePagesInMetaspace = false {product}
GC
위에서 이야기한 것처럼 Metaspace 공간의 릴리즈는 오직 class가 unload되고 GC가 일어날 때만 발생한다. 따라서, Heap입장에서는 GC를 통해 얻을 수 있는 것이 많지 않은 경우라도, metaspace내의 오래된 class metadata 청소를 위해 GC가 수행되는 것이 효율적일 수 있다.
Metaspace에서 유도되는 GC는 대표적으로 두가지가 있다.
- Metaspace 할당 시: 지속적으로 metadata를 쌓으면서 metaspace를 확장시키는 대신, Metaspace의 효율적 활용을 위해 어느 임계지점에서는 오래된 class loader들을 GC를 통해 정리하는 시도를 한다.
- Metaspace OOM 시: 커밋된 메모리의 합계가 MaxMetaspaceSize를 넘었거나, Compressed Class Space가 부족할 때 GC가 발생할 수 있다.
Metaspace Monitoring
Metaspace의 사용량을 모니터링해보자.
jstat을 통해 현재 Metaspace의 가용량 중 활용량이 %로 표출되는 것을 확인할 수 있다. jstat의 추가 정보는 java doc을 참고하자.
jstat -gcutil <pid> | awk ‘{print($5)}’
Java11부터는 jcmd 커맨드를 통해 훨씬 상세한 모니터링이 가능하다.
jcmd <pid or process name> VM.metaspace
예시를 한번 보자.
$ jcmd wildfly VM.metaspace31997:Total Usage ( 1041 loaders):Non-Class: 2837 chunks, 58,62 MB capacity, 53,54 MB ( 91%) used, 4,90 MB ( 8%) free, 2,59 KB ( <1%) waste, 177,31 KB ( <1%) overhead, deallocated: 5065 blocks with 1,01 MBClass: 1653 chunks, 9,93 MB capacity, 7,44 MB ( 75%) used, 2,40 MB ( 24%) free, 208 bytes ( <1%) waste, 103,31 KB ( 1%) overhead, deallocated: 653 blocks with 285,77 KBBoth: 4490 chunks, 68,55 MB capacity, 60,98 MB ( 89%) used, 7,29 MB ( 11%) free, 2,79 KB ( <1%) waste, 280,62 KB ( <1%) overhead, deallocated: 5718 blocks with 1,29 MBVirtual space:Non-class space: 60,00 MB reserved, 58,75 MB ( 98%) committed
Class space: 248,00 MB reserved, 10,00 MB ( 4%) committed
Both: 308,00 MB reserved, 68,75 MB ( 22%) committedChunk freelists:Non-Class:specialized chunks: 1, capacity 1,00 KB
small chunks: 11, capacity 44,00 KB
medium chunks: (none)
humongous chunks: (none)
Total: 12, capacity=45,00 KBClass:specialized chunks: (none)
small chunks: 2, capacity 4,00 KB
medium chunks: (none)
humongous chunks: (none)
Total: 2, capacity=4,00 KBWaste (percentages refer to total committed size 68,75 MB):Committed unused: 156,00 KB ( <1%)
Waste in chunks in use: 2,79 KB ( <1%)
Free in chunks in use: 7,29 MB ( 11%)
Overhead in chunks in use: 280,62 KB ( <1%)In free chunks: 49,00 KB ( <1%)Deallocated from chunks in use: 1,29 MB ( 2%) (5718 blocks)-total-: 9,06 MB ( 13%)MaxMetaspaceSize: 256,00 MB
InitialBootClassLoaderMetaspaceSize: 4,00 MB
UseCompressedClassPointers: true
CompressedClassSpaceSize: 248,00 MB