压缩类空间(CCS)是JVM在本地内存中硬性切出的独立地址区间,专用于存放Klass结构体,与Metaspace并列且物理隔离、互不共享页表、不参与GC。

压缩类空间(Compressed Class Space,简称 CCS)的“精准隔离”,不是逻辑上的软划分,而是 JVM 在本地内存中**硬性切出的一块独立地址区间**,由操作系统直接分配、不参与 GC、也不与 Metaspace 共享页表或内存池。它的物理本质,是 HotSpot 虚拟机在启用 -XX:+UseCompressedClassPointers 时,主动向 OS 申请的一段**只读+可执行(RWX)的匿名内存映射区域**,专用于存放 Klass 结构体。
它是一块“独占”的本地内存段
CCS 不是堆的一部分,也不是 Metaspace 的子区域——它和 Metaspace 并列,都位于本地内存(Native Memory),但彼此地址不重叠、管理机制完全分离:
- JVM 启动时通过
mmap(MAP_ANONYMOUS)单独申请一块连续虚拟地址空间(默认 1GB),并立即提交(commit)对应物理页; - 这块空间一旦划出,就固定归属 CCS,JVM 不会把它挪给 Metaspace、CodeCache 或线程栈用;
- 它不经过 JVM 内存分配器(如 Arena 或 Chunk 管理),而是由 KlassLoader 直接在该区域内按需分配 Klass 对象(每个约 2–4KB);
- 没有 GC 线程扫描它,也没有元数据卸载机制——Klass 结构体生命周期绑定类加载器,但 CCS 本身不回收碎片,只等整个空间耗尽。
隔离的底层支撑:指针解引用路径的硬件级分叉
每个 Java 对象头里有个 Klass* 字段。开启压缩后,这个指针是 32 位,但它不是直接指向 Metaspace,而是被 JVM 解码为一个**相对于 CCS 基址的偏移量**:
- OS 给 CCS 分配的是一段虚拟地址(比如
0x00007f8a00000000 ~ 0x00007f8a40000000); - JVM 把这个起始地址记作
compressed_class_space_base; - 运行时,CPU 拿到 32 位压缩值后,左移 3 位(对齐到 8 字节边界),再加 base 地址,才真正访问到 Klass 结构体;
- 这个地址计算由 JIT 编译器硬编码进指令流(如
lea rax, [rbx + rdx*8 + 0x7f8a00000000]),完全绕过 Metaspace 的地址空间。
为什么不能靠调大 MaxMetaspaceSize 来缓解 CCS OOM
因为两者根本不在同一张内存页表里:
-
-XX:MaxMetaspaceSize控制的是 Metaspace 的虚拟内存上限(通过sbrk或mmap动态扩展),但每次扩展申请的是新页,地址随机; - CCS 的大小在 JVM 初始化阶段就锁定,之后不可扩容,也不响应 Metaspace 的 GC 回收行为;
- 即使 Metaspace 还剩 500MB,只要 CCS 的 1GB 地址空间里所有可用 slot 都被 Klass 占满(例如加载了 50 万个类,每个占 2KB),就会立即抛
OutOfMemoryError: Compressed class space; - 错误发生时,JVM 甚至不会尝试触发 Metaspace GC——它连看都不看 Metaspace 一眼。
精准隔离带来的可观测性特征
这种物理隔离让 CCS 行为高度确定,也容易验证:
-
jcmd <pid> VM.native_memory summary</pid>中能看到单独的Class行,显示 committed 和 reserved 大小,与Metaspace行数值无关; -
jstat -gc <pid></pid>不显示 CCS 使用率——它压根不纳入 GC 统计维度; - 用
pstack或perf record抓取热点,能观察到大量Klass::cast_to_instance_klass等函数集中在 CCS 地址段内执行; - Linux 下
cat /proc/<pid>/maps | grep "class"</pid>可直接看到 CCS 对应的内存映射行(带rw-标志,无heap或anon关键字)。

















