枚举 exeeded 65535 字节的静态初始值设定项限制...什么是最好的办法?

2022-09-02 10:42:40

我已经启动了一个相当大的枚举,即所谓的描述符,我想将其用作模型中的参考列表。但是现在我第一次遇到了编译器/ VM限制,所以我正在寻找处理这个问题的最佳解决方案。

这是我的错误:静态初始值设定项的代码超过65535字节限制

很明显,这来自哪里 - 我的Enum只是有很多元素。但是我需要这些元素 - 没有办法减少这一套。

首先,我计划使用单个枚举,因为我想确保枚举中的所有元素都是唯一的。它在休眠持久性上下文中使用,其中对枚举的引用作为字符串值存储在数据库中。所以这一定是独一无二的!

我的Enum的内容可以分为几组元素,这些元素属于一起。但是拆分枚举会消除我在编译时获得的独特安全性。或者这可以以某种方式通过多个枚举来实现吗?

我目前唯一的想法是定义一些称为描述符的接口,并编写几个Enum来实现它。通过这种方式,我希望能够像使用单个枚举一样使用Hibernate Enum映射。但我甚至不确定这是否有效。而且我失去了独特的安全性。

任何想法如何处理这个案件?


答案 1

简单。不要用于此目的。你不能。这是行不通的。enum

您的源代码可能没有显式引用许多枚举值。相反,您使用的枚举是在唯一对象实例和字符串名称之间进行映射的便捷方法。因此,只需将枚举类型替换为显式管理映射的类型,并通过从文件或数据库读取来初始化映射即可。如果你做得对,你将获得枚举的计算属性和类型安全性。你唯一失去的是句法糖...和静力学。

这种方法将具有额外的优势,即您可以在不修改程序源代码的情况下修改“描述符”映射。


顺便说一句,您遇到的限制是由JVM类文件格式强加的。方法或构造函数的大小上限为 2^16 字节,类静态初始化代码表示为具有时髦名称的特殊方法。

更新

不幸的是,您的自我回答解决方案仍然会遇到不同的64K限制...如果推得太远。拆分方法可以绕过方法大小限制,但类常量池中的条目数也有 64K 的限制。每个字符串文本都需要一个常量池条目。initialize()


答案 2

这不是一个简单的解决方案,但您可以尝试...修补 Java 编译器。

当您编写 一个 时,Java 编译器会生成一个扩展的类(如果存在特定于常量的方法,则可能有几个类)。该类具有一些(隐藏的)静态字段,在字节码级别,这些字段使用特殊方法(JVM在首次使用该类时调用该方法)进行初始化。与任何其他方法一样,该方法的代码限制为 65535 字节。每个常量在字节码中贡献大约 20 到 22 个字节(如果有特定于常量的构造函数,则更多),因此在大约 3000 个枚举常量处达到限制。enumjava.lang.Enum<clinit>()<clinit>()<clinit>()

现在,该方法有一个有趣的名称,但它并不是什么特别之处。它可以调用其他方法。Java编译器可以将猛犸象分成几个隐藏的子方法,然后一个接一个地调用。Java编译器目前没有这样做,但理论上可以。结果可由任何 JRE 处理。<clinit>()<clinit>()<clinit>()

或者,综合合成枚举类,从专用程序生成字节码,该程序本身可能是用 Java 编写的。从本质上讲,这就像为特定目标编写自己的专用编译器并使用自己的源语法一样。BCEL 图书馆可能会有所帮助。

请注意,还有其他限制可能会跳到您身上。对于每个枚举常量,静态代码(中的一个)使用两个“常量”,它们是在已编译类的“常量池”部分中聚合的内部值。这两个值是常量名称(字符串形式)和生成的静态字段引用。对 65536 个常量池条目有硬性限制(索引位于 16 位上),因此枚举常量不超过 32000 个。一个打补丁的Java编译器可以通过生成几个隐藏类来绕过这个限制,每个类都有自己的常量池。一个更难的限制是静态字段的数量:每个枚举常量都成为“enum”类中的静态字段,并且一个类中不能超过65535个字段(静态或非静态)。<clinit>()