“代理类很多”与“元空间泄漏”之间还隔着缓存键、类加载器和引用存活期。只看某个时刻已经加载的类数无法判断它们还在增长、已经可卸载还是确实被长寿命对象保留。CGLIB 的 AbstractClassGenerator 公开了缓存相关行为真实排查应跟到部署版本里的代码路径而不是笼统地说“打开缓存就好了”。源码 同一个父类也可能因为接口集合、回调类型、命名策略或加载器不同而生成不同形状的代理。先数稳定的形状再数类我会先记录代理创建请求的结构化键观察重复请求是否真的重复。若每次把 UUID 放进键里即使缓存功能开启命中率也不会好。反过来若忽略了决定类行为的字段只追求同一个键可能缓存出行为错误的类。然后才比较类加载速率、卸载情况、加载器数量和堆引用链。它们是互相补充的证据。某次 GC 是否卸载类要看实际收集器与配置不能从“Full GC 发生过”推导“类加载器一定已释放”。用 Python 展示键空间明确不冒充 JVM 复现下面用动态type作为“生成一种形状”的教学替身。重复的规范化键只创建一次换了加载器命名空间后则故意创建另一份。这能验证缓存键设计不能测量 Java 元空间。图对比稳定缓存键与变化的键空间。缓存只是减少重复生成的一种手段它自己若长时间持有加载器或类也可能扩大存活对象集合必须一起检查生命周期。class ShapeFactory: def __init__(self): self.cache {} self.generated 0 def get(self, loader_namespace, base_name, methods): key (loader_namespace, base_name, tuple(sorted(set(methods)))) if key not in self.cache: self.generated 1 self.cache[key] type(Shape str(self.generated), (), {shape_key: key}) return self.cache[key] factory ShapeFactory() a factory.get(loader-1, Order, [save, read]) b factory.get(loader-1, Order, [read, save, read]) assert a is b and factory.generated 1 c factory.get(loader-2, Order, [read, save]) assert c is not a and factory.generated 2 assert len(factory.cache) 2 print(same shape reused; loader namespace creates a distinct shape)封面类加载器图标表示类与加载器的引用需要核对并不表示每个代理类一定创建一个新加载器多个生成类可以属于同一个加载器。例子跑通以后还要验证什么这段代码已在本地 macOS、CPython 3.9.6 下运行实际输出为same shape reused; loader namespace creates a distinct shape。它验证的是例子中的数据约束与分支不包含线上服务的吞吐或延迟测量。没有运行 Java、CGLIB、JFR 或元空间压力测试。Python 动态类和 JVM 类卸载机制不同例子不提供元空间容量、48 万代理类或“十秒恢复”的结论。实际复现需要固定 JDK、框架依赖、加载器层次与代理配置并记录完整的类加载与卸载证据。延伸阅读CGLIB AbstractClassGenerator以实际源码核对缓存路径master 不是部署版本的替代品。
阅读完成 · 觉得有帮助?