compressed class space 是 jvm 启用压缩类指针时额外划分的独立本地内存区域,专门存储 klass 结构体;它与 metaspace 并列存在、内存不重叠,metaspace 存完整类元数据,而 compressed class space 仅存轻量级 klass 对象。

Compressed Class Space 是什么,和 Metaspace 什么关系
它不是 Metaspace 的子集,而是 JVM 在启用 -XX:+UseCompressedClassPointers 时,**额外划分的一块独立本地内存区域**,专门存放被压缩的类元数据指针(Klass*)所指向的结构体。Metaspace 存的是类的完整元数据(方法表、常量池等),而 Compressed Class Space 存的是这些元数据的“索引头”——即每个 java.lang.Class 实例背后那个轻量级的 Klass 对象。
关键点在于:两者内存不重叠、可分别耗尽、错误信息也不同:
-
java.lang.OutOfMemoryError: Metaspace→ Metaspace 区域满(类元数据本身太多) -
java.lang.OutOfMemoryError: Compressed class space→ Compressed Class Space 满(Klass 结构体数量超限)
默认大小是 1GB(JDK8u60+),由 -XX:CompressedClassSpaceSize=1g 控制;它不随 -XX:MaxMetaspaceSize 变化,也不能靠调大后者来缓解前者溢出。
为什么 Compressed Class Space 会成为类加载上限的隐形瓶颈
一个 Class 对象对应一个 Klass 结构体,而每个 Klass 至少占 2KB(x64 下典型值)。当应用频繁生成动态类(CGLIB、Groovy、Lombok 编译期代理、Spring AOP 切面类),或使用大量模块/插件(OSGi、自定义类加载器隔离场景),Klass 实例数会指数级增长。此时即使 Metaspace 还有余量,Compressed Class Space 也可能先爆。
常见触发场景:
- Spring Boot DevTools 启用热重载 + 多次修改后反复加载新类
- MyBatis 动态生成 Mapper 接口代理类,且未复用
SqlSession或MapperProxyFactory - 使用
Unsafe.defineAnonymousClass或Lookup.defineHiddenClass(JDK15+)创建大量隐藏类 - 微服务网关类加载器隔离策略下,每个租户/路由规则加载独立类集
怎么确认是不是 Compressed Class Space 耗尽导致类加载失败
不能只看 GC 日志或 jstat -gc,因为它们不暴露 Compressed Class Space 使用率。必须结合三类信号交叉验证:
- 错误日志中明确出现
OutOfMemoryError: Compressed class space(注意拼写,空格不能少) - JMX 查询
java.lang:type=MemoryPool,name=Compressed Class Space,检查Usage.used是否接近Usage.max - 用
jcmd <pid> VM.native_memory summary scale=MB</pid>查看 “Class” 分区的 committed 值是否逼近-XX:CompressedClassSpaceSize设置值
特别注意:jstat -gc 输出里的 CCSU(Compressed Class Space Used)字段仅在 JDK10+ 支持,旧版本看不到;若你用的是 JDK8u292 或 JDK11.0.11,别指望它显示这个值。
调优建议与容易踩的坑
直接调大 -XX:CompressedClassSpaceSize 最快,但治标不治本。真正要解决类加载上限问题,得从源头控制 Klass 数量:
- 禁用不必要的动态类生成:Spring Boot 中设
spring.aop.proxy-target-class=false(改用接口代理)、MyBatis 关闭configuration.useGeneratedKeys=true等非必需特性 - 避免在运行时高频创建新类加载器:Web 应用热部署场景下,优先用
ClassLoader.close()(JDK9+)或显式清空其内部缓存(如 Tomcat 的WebappClassLoaderBase.clearReferencesThreads()) - 慎用
-XX:+UseCompressedClassPointers:如果堆已超 32GB 或明确知道类总数 > 50 万,压缩反而增加间接寻址开销,此时应关闭该选项并接受更大的 Klass 内存占用 - 监控比调参更重要:通过
-XX:+PrintGCDetails -Xlog:metaspace*=debug(JDK10+)捕获每次类加载对应的 Klass 分配位置,定位哪段代码批量 new Class
最常被忽略的一点:Compressed Class Space 的大小必须是 2MB 的整数倍,且不能小于 1MB。设成 -XX:CompressedClassSpaceSize=1500m 会被 JVM 自动向下取整为 1024m,导致你以为加了 500MB 实际没生效。










