枚举超出静态初始化程序的 65535 字节限制...最好做什么?

发布于 2024-08-27 11:18:57 字数 591 浏览 4 评论 0原文

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

这是我的错误:静态初始化程序的代码超出了 65535 字节限制

很明显这是从哪里来的 - 我的 Enum 只是包含太多元素。但我需要这些元素 - 无法减少该集合。

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

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

我当前唯一的想法是定义一些名为 Descriptor 的接口,并编写几个实现它的枚举。这样我希望能够像使用单个 Enum 一样使用 Hibernate Enum 映射。但我什至不确定这是否有效。我失去了独特的安全感。

有什么想法如何处理这种情况吗?

I've started a rather large Enum of so called Descriptors that I've wanted to use as a reference list in my model. But now I've come across a compiler/VM limit the first time and so I'm looking for the best solution to handle this.

Here is my error : The code for the static initializer is exceeding the 65535 bytes limit

It is clear where this comes from - my Enum simply has far to much elements. But I need those elements - there is no way to reduce that set.

Initialy I've planed to use a single Enum because I want to make sure that all elements within the Enum are unique. It is used in a Hibernate persistence context where the reference to the Enum is stored as String value in the database. So this must be unique!

The content of my Enum can be devided into several groups of elements belonging together. But splitting the Enum would remove the unique safety I get during compile time. Or can this be achieved with multiple Enums in some way?

My only current idea is to define some Interface called Descriptor and code several Enums implementing it. This way I hope to be able to use the Hibernate Enum mapping as if it were a single Enum. But I'm not even sure if this will work. And I loose unique safety.

Any ideas how to handle that case?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

叶落知秋 2024-09-03 11:18:57

简单的。不要为此使用enum。你不能。这是行不通的。

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

这种方法还有一个额外的优点,即您可以修改“描述符”映射,而无需修改程序的源代码。


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

更新

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

Simple. Don't use enum for this. You can't. It won't work.

The chances are that your source code does not explicitly refer to many of the enum values. Rather, you are using the enum as a convenient way to map between unique object instances and string names. So just replace the enum type with a type that explicitly manages the mapping, initializing it by reading from a file or database. If you do it right, you will get the computational properties and type-safety of an enum. The only thing you lose is the syntactic sugar ... and the statics.

This approach will have the added advantage that you can modify the 'descriptors' mapping without modifying the source code of your program.


By the way, the limitation you are running into is imposed by the JVM class file format. A method or constructor has an upper size limit of 2^16 bytes, and a classes static initialization code is represented as a special method with a funky name.

UPDATE

Unfortunately your self-answer solution will still run into a different 64K limit ... if pushed too far. Splitting the initialize() method gets around the method size limit, but there is also a 64K limit on the number of entries in a classes constant pool. Each String literal requires a constant pool entry.

忆依然 2024-09-03 11:18:57

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

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

现在,() 方法有一个有趣的名称,但它并没有什么特别之处;它可以调用其他方法。 Java 编译器可以将庞大的 () 拆分为几个隐藏的子方法,然后 () 会调用这些子方法一个接一个。 Java 编译器目前无法做到这一点,但理论上可以。结果可由任何 JRE 处理。

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

请注意,还有其他限制可能会给您带来麻烦。对于每个枚举常量,静态代码(() 中的代码)使用两个“常量”,它们是聚合在编译类的“常量池”部分中的内部值。这两个值是常量名称(作为字符串)和生成的静态字段引用。常量池条目有 65536 个硬限制(索引为 16 位),因此枚举常量最多不超过 32000 个。经过修补的 Java 编译器可以通过生成多个隐藏类来绕过该限制,每个隐藏类都有自己的常量池。更严格的限制是静态字段的数量:每个枚举常量都成为“enum”类中的静态字段,并且一个类中的字段(静态或非静态)不能超过 65535 个。

This is not a simple solution, but you may try to... patch the Java compiler.

When you write an enum, the Java compiler generate a class which extends java.lang.Enum (possibly several classes, if there are constant-specific methods). The class has some (hidden) static fields, which, at the bytecode level, are initialized with the special <clinit>() method (which the JVM calls when the class is first used). As any other method, the code for the <clinit>() method is limited to 65535 bytes. Each constant contributes to about 20 to 22 bytes in the <clinit>() bytecode (more if there are constant-specific constructors), so you hit the limit at about 3000 enumeration constants.

Now the <clinit>() method has a funny name but it is nothing really special; it can invoke other methods. The Java compiler could split the mammoth <clinit>() into several hidden sub-methods which <clinit>() would then invoke one after the other. The Java compiler does not currently do that, but it theoretically could. The result would be processable by any JRE.

Alternatively, synthesize your enum class synthetically, generating bytecode from a dedicated program, possibly itself written in Java. In essence, this is like writing your own specialized compiler, for a specific target and using your own source syntax. The BCEL library may help.

Note that there are other limits which could jump on you. For each enumeration constant, the static code (the one in <clinit>()) uses two "constants", which are internal values aggregated in the "constant pool" part of the compiled class. The two values are the constant name (as a string) and the resulting static field reference. There is a hard limit on 65536 constant pool entries (indexes are on 16 bits), so no more than a bit more than 32000 enumeration constants. A patched Java compiler could walk around that limit by generating several hidden classes, each with its own constant pool. A harder limit is in the number of static fields: each enumeration constant becomes a static field in the "enum" class, and there can be no more than 65535 fields (static or not) in a class.

两人的回忆 2024-09-03 11:18:57

您可以尝试 类型安全枚举模式
由 Joshua Bloch 在他的书《Effective Java》第一版中描述。

这个想法是创建一个带有私有构造函数的类。在此类中,您可以定义任意数量的静态实例,就像在 Java 枚举中一样。

不过,我不知道 Java 语言中静态成员的数量是否有限制。

You can try the typesafe enum pattern
described by Joshua Bloch in the first edition of his book "Effective Java".

The idea is to create a class with a private constructor. Inside this class you define static instances of any number you want to-just as in a Java enum.

I don't know if there is a limit for the number of static members in the Java language, though.

流心雨 2024-09-03 11:18:57

您可以尝试在顶级类中嵌套静态内部类

You might try nesting static inner-classes within a top-level class

栖迟 2024-09-03 11:18:57

我最初的想法是使用 @Enumerated 注释来映射 Enum。这看起来像下面的示例:

@Enumerated(STRING)
private DescriptorEnum descriptor;

数据库将有一个名为 DESCRIPTOR 的 varchar 类型列,Hibernate(在我的例子中)会将字符串映射到枚举。

但我有 65k 的限制(参见问题),这对我来说太小了。但我找到了解决方案。看一下下面的示例:

public final class Descriptor {
    public final String acronym;
    private static final Hashtable<String, Descriptor> all = new Hashtable<String, Descriptor>();
    static {
        initialize();
    }
    private static void initialize() {
        new Descriptor("example001");
        new Descriptor("example002");
        new Descriptor("example003");
    }
    private Descriptor(String acronym) {
        this.acronym = acronym;
        if (all.contains(this.acronym)) {
            throw new RuntimeException("duplicate acronym: " + this.acronym);
        }
        all.put(this.acronym, this);
    }
    public static Descriptor valueOf(String acronym) {
        return all.get(acronym);
    }
    public String value() {
        return this.acronym;
    }
}

这个 Descriptor 类模拟了典型枚举的用法。但现在我可以将initialize() 方法拆分为多个方法,以解决方法也存在的65k 限制。枚举不允许将初始化分成几个块 - 我的类允许。

现在我必须使用稍微不同的映射:

@Column(name = "DESCRIPTOR")
private String                  descriptorAcronym       = null;
private transient Descriptor    descriptor              = null;
public Descriptor getDescriptor() {
    return descriptor;
}
public void setDescriptor(Descriptor desc) {
    this.descriptor = desc;
    this.descriptorAcronym = desc != null ? desc.acronym : null;
}
public String getDescriptorAcronym() {
    return descriptorAcronym;
}
public void setDescriptorAcronym(String desc) {
    this.descriptorAcronym = desc;
    this.descriptor = desc != null ? Descriptor.valueOf(desc) : null;
}
@PostLoad
private void syncDescriptor() {
    this.descriptor = this.descriptorAcronym != null ? Descriptor.valueOf(this.descriptorAcronym) : null;
}

这样在大多数情况下我可以像枚举一样使用该类。这有点棘手......但似乎有效。感谢您的所有意见,最终使我找到了该解决方案。

My original idea was to map the Enum using the @Enumerated annotion. This would have looked like the following example:

@Enumerated(STRING)
private DescriptorEnum descriptor;

The database would have a column called DESCRIPTOR of type varchar and Hibernate (in my case) would map the string to the enumeration.

But I have that limit of 65k (see question) which is to small in my case. But I've found a solution. Have a look at the following example:

public final class Descriptor {
    public final String acronym;
    private static final Hashtable<String, Descriptor> all = new Hashtable<String, Descriptor>();
    static {
        initialize();
    }
    private static void initialize() {
        new Descriptor("example001");
        new Descriptor("example002");
        new Descriptor("example003");
    }
    private Descriptor(String acronym) {
        this.acronym = acronym;
        if (all.contains(this.acronym)) {
            throw new RuntimeException("duplicate acronym: " + this.acronym);
        }
        all.put(this.acronym, this);
    }
    public static Descriptor valueOf(String acronym) {
        return all.get(acronym);
    }
    public String value() {
        return this.acronym;
    }
}

This Descriptor class simulates the usage of a typical enum. But now I'm able to split the initialize() method into several ones that work around the 65k limit that also exists for methods. The Enum doesn't allow to split the initialization into several chunks - my class does.

Now I have to use a slightly different mapping though:

@Column(name = "DESCRIPTOR")
private String                  descriptorAcronym       = null;
private transient Descriptor    descriptor              = null;
public Descriptor getDescriptor() {
    return descriptor;
}
public void setDescriptor(Descriptor desc) {
    this.descriptor = desc;
    this.descriptorAcronym = desc != null ? desc.acronym : null;
}
public String getDescriptorAcronym() {
    return descriptorAcronym;
}
public void setDescriptorAcronym(String desc) {
    this.descriptorAcronym = desc;
    this.descriptor = desc != null ? Descriptor.valueOf(desc) : null;
}
@PostLoad
private void syncDescriptor() {
    this.descriptor = this.descriptorAcronym != null ? Descriptor.valueOf(this.descriptorAcronym) : null;
}

This way I can use the class like an Enum in most cases. It's a bit tricky... but it seems to work. Thanks for all the input that finally led me to that solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文