bigywater.tech
Java · 341

元空间碎片:类元数据为什么不只看容量

看元空间别只盯着 used:capacity、committed、free chunks 是不同的账。这篇用 jcmd VM.metaspace 与 MemoryPoolMXBean 同时读数,把加载器全部/部分丢弃后的变化量出来。

106.92 → 3.82 MB
300 个加载器全部丢弃并 GC 后 JDK 21 的 committed(jcmd Both 行)
78.81 vs 55.50 MB
同样丢弃一半加载器,交错丢弃与丢弃前一半之后虚拟空间的 committed
105472 KB
JDK 17 上 MetaspaceReclaimPolicy=none 时丢弃后 committed 仍然没降

capacity / committed / used,以及全部丢弃之后

实测 · 本机执行输出
  [默认]
    起点                                             MXBean used=   619 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.32 MB ( 74%) used, 123.41 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器(各 150 个类)                          MXBean used=100457 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.10 MB ( 91%) used, 8.81 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    Total Usage - 343 loaders, 46753 classes (1105 shared):
    全部丢弃并 GC 之后                                    MXBean used=  3801 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.71 MB ( 82%) used, 111.09 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated
    Total Usage - 43 loaders, 1754 classes (1105 shared):

MetaspaceReclaimPolicy 在 JDK 21 上被忽略

实测 · 本机执行输出
  [MetaspaceReclaimPolicy=balanced]
    起点                                             MXBean used=   616 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.32 MB ( 74%) used, 124.60 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器(各 150 个类)                          MXBean used=100456 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.10 MB ( 91%) used, 8.81 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    Total Usage - 343 loaders, 46753 classes (1105 shared):
    全部丢弃并 GC 之后                                    MXBean used=  3801 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.71 MB ( 82%) used, 111.64 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated
    Total Usage - 43 loaders, 1754 classes (1105 shared):
  OpenJDK 64-Bit Server VM warning: Ignoring option MetaspaceReclaimPolicy; support was removed in 21.0
  [MetaspaceReclaimPolicy=aggressive]
    起点                                             MXBean used=   616 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.31 MB ( 74%) used, 128.76 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器(各 150 个类)                          MXBean used=100455 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.10 MB ( 91%) used, 8.82 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    Total Usage - 343 loaders, 46753 classes (1105 shared):
    全部丢弃并 GC 之后                                    MXBean used=  3800 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.71 MB ( 82%) used, 112.75 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated
    Total Usage - 43 loaders, 1754 classes (1105 shared):
  OpenJDK 64-Bit Server VM warning: Ignoring option MetaspaceReclaimPolicy; support was removed in 21.0
  [MetaspaceReclaimPolicy=none]
    起点                                             MXBean used=   617 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.32 MB ( 74%) used, 117.80 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器(各 150 个类)                          MXBean used=100463 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.11 MB ( 91%) used, 8.81 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    Total Usage - 343 loaders, 46753 classes (1105 shared):
    全部丢弃并 GC 之后                                    MXBean used=  3808 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.72 MB ( 82%) used, 104.59 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated
    Total Usage - 43 loaders, 1754 classes (1105 shared):
  OpenJDK 64-Bit Server VM warning: Ignoring option MetaspaceReclaimPolicy; support was removed in 21.0

丢弃一半:交错与前一半

实测 · 本机执行输出
  [interleave]
    起点                                             MXBean used=   623 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.32 MB ( 74%) used, 122.69 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器                                    MXBean used=100446 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.09 MB ( 91%) used, 8.82 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    丢弃一半(interleave)并 GC 之后                        MXBean used= 52120 KB committed= 71168 KB | Both: 2642 chunks, 56.07 MB capacity, 55.38 MB ( 99%) committed, 50.90 MB ( 91%) used, 4.48 MB ( 8%) free, 2.34 KB ( <1%) waste , dealloc
    Total Usage - 193 loaders, 24254 classes (1105 shared):
    再丢弃另一半并 GC 之后                                  MXBean used=  3791 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.70 MB ( 82%) used, 121.55 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
  [firsthalf]
    起点                                             MXBean used=   618 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.31 MB ( 74%) used, 127.05 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 300 个加载器                                    MXBean used=100456 KB committed=109568 KB | Both: 5191 chunks, 107.61 MB capacity, 106.92 MB (>99%) committed, 98.10 MB ( 91%) used, 8.81 MB ( 8%) free, 4.69 KB ( <1%) waste , deall
    丢弃一半(firsthalf)并 GC 之后                         MXBean used= 52129 KB committed= 56896 KB | Both: 2642 chunks, 56.07 MB capacity, 55.38 MB ( 99%) committed, 50.91 MB ( 91%) used, 4.47 MB ( 8%) free, 2.34 KB ( <1%) waste , dealloc
    Total Usage - 193 loaders, 24254 classes (1105 shared):
    再丢弃另一半并 GC 之后                                  MXBean used=  3801 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.71 MB ( 82%) used, 111.91 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated

一个加载器最少占多少

实测 · 本机执行输出
  [1 个类 ×1000]
    起点                                             MXBean used=   619 KB committed=   768 KB | Both: 88 chunks, 4.50 MB capacity, 3.44 MB ( 76%) committed, 3.32 MB ( 74%) used, 119.87 KB ( 3%) free, 0 bytes ( 0%) waste , deallocated
    保留 1000 个加载器(各 1 个类)                           MXBean used=  5930 KB committed=  9920 KB | Both: 2092 chunks, 10.37 MB capacity, 9.68 MB ( 93%) committed, 5.79 MB ( 56%) used, 3.89 MB ( 38%) free, 0 bytes ( 0%) waste , deallocat
    全部丢弃并 GC 之后                                    MXBean used=  3814 KB committed=  3968 KB | Both: 92 chunks, 4.51 MB capacity, 3.82 MB ( 85%) committed, 3.73 MB ( 83%) used, 98.38 KB ( 2%) free, 0 bytes ( 0%) waste , deallocated:

碎片去了哪:In free chunks

实测 · 本机执行输出
  [交错丢弃 第 1 次]
    池:Metaspace type=Non-heap memory
    池:Compressed Class Space type=Non-heap memory
    保留 300 个: 两个池合计 used=124951 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64524 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
    再等 3 秒 + GC: 两个池合计 used=64524 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
  [交错丢弃 第 2 次]
    保留 300 个: 两个池合计 used=124954 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64527 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
    再等 3 秒 + GC: 两个池合计 used=64527 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
  [交错丢弃 第 3 次]
    保留 300 个: 两个池合计 used=124943 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64516 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
    再等 3 秒 + GC: 两个池合计 used=64516 KB committed=109952 KB | Both: 1.12 GB reserved, 78.81 MB  committed. | In free chunks: 32.79 MB ( 42%)
  [丢弃前一半 第 1 次]
    池:Metaspace type=Non-heap memory
    池:Compressed Class Space type=Non-heap memory
    保留 300 个: 两个池合计 used=124950 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64523 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)
    再等 3 秒 + GC: 两个池合计 used=64523 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)
  [丢弃前一半 第 2 次]
    保留 300 个: 两个池合计 used=124951 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64523 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)
    再等 3 秒 + GC: 两个池合计 used=64523 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)
  [丢弃前一半 第 3 次]
    保留 300 个: 两个池合计 used=124946 KB committed=138752 KB | Both: 1.12 GB reserved, 106.94 MB  committed. | In free chunks: 0 bytes ( 0%)
    丢弃一半 + GC 后立即: 两个池合计 used=64522 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)
    再等 3 秒 + GC: 两个池合计 used=64522 KB committed=71744 KB | Both: 1.12 GB reserved, 55.50 MB  committed. | In free chunks: 172.00 KB ( <1%)

JDK 17 的 MetaspaceReclaimPolicy

实测 · 本机执行输出
  [JDK 17 MetaspaceReclaimPolicy=balanced]
    起点                                             MXBean used=   612 KB committed=   768 KB 
    保留 300 个加载器(各 150 个类)                          MXBean used= 96804 KB committed=104000 KB 
    全部丢弃并 GC 之后                                    MXBean used=  3046 KB committed=  3200 KB 
  [JDK 17 MetaspaceReclaimPolicy=aggressive]
    起点                                             MXBean used=   610 KB committed=   656 KB 
    保留 300 个加载器(各 150 个类)                          MXBean used= 96788 KB committed=103888 KB 
    全部丢弃并 GC 之后                                    MXBean used=  3030 KB committed=  3104 KB 
  [JDK 17 MetaspaceReclaimPolicy=none]
    起点                                             MXBean used=   609 KB committed=  4480 KB 
    保留 300 个加载器(各 150 个类)                          MXBean used= 96790 KB committed=105472 KB 
    全部丢弃并 GC 之后                                    MXBean used=  3032 KB committed=105472 KB 

分步演示

元空间碎片
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(主要)与 Temurin 17(第 6 节),G1,-Xmx512m;每个类是自动生成的 4 个小方法的小类;取数用 DiagnosticCommand MBean 的 vmMetaspace(与 jcmd VM.metaspace 同源)和 MemoryPoolMXBean
    • 没有验证:交错丢弃之后那些空闲 chunk 没被还给操作系统的具体原因(只观察到数量,没有读源码或用 -Xlog:metaspace 求证)、更长时间或再次分配后这些空闲 chunk 是否被复用并被回收、类空间(Compressed Class Space)与非类空间的分别表现、Linux 上的行为(RSS 与 commit 的关系)、多线程并发创建加载器时的情形
    京ICP备2026067502号-1京公网安备11010502063487号