[Java] 자바 메타스페이스(Metaspace)에 대해 알아보자.

Jaemun Jung
11 min readNov 3, 2021

메타스페이스(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 영역에 위치한다는 점이다.

아래 그림을 참고하자.

Java7 vs Java8 JVM (from: https://www.programmersought.com/article/4905216600/)

따라서 “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될 때만 일어난다.
아래 그림을 한번 보자.

Metaspace allocation and release (image from https://stuefe.de/posts/metaspace/what-is-metaspace/#fn:1)

즉, 릴리즈는 어떤 live instance도 없고 어떤 참조도 없는 상태에서 GC가 일어난 이후에야 릴리즈 되는 것이다.

MaxMetaspaceSize, CompressedClassSpaceSize

영역별 Flag

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)}’
jstat 조회

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%) committed
Chunk 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 KB
Class:specialized chunks: (none)
small chunks: 2, capacity 4,00 KB
medium chunks: (none)
humongous chunks: (none)
Total: 2, capacity=4,00 KB
Waste (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

References

--

--