垃圾回收器是否适用于枚举类型?

37
根据jls § 8.9.2 枚举体声明,枚举类型声明声明终结器是一个编译时错误。 枚举类型的实例永远不会被完成。
由于终结器在垃圾收集器运行之前执行,如果没有终结器,这是否意味着enum类型始终保留在内存中,并且垃圾回收器无法应用于enum类型?

我相信枚举本身不能被收集,但常量值中的弱引用可以被收集。 - Rogue
如果枚举是因为没有引用而被收集的,那么如果稍后创建了使用该枚举的对象,会发生什么? - paparazzo
枚举只是普通的类/实例,没有太多的魔法 - 有一点编译器和一点序列化。它们扩展了java.lang.Enum,并且编译器为每个枚举常量添加了静态final实例。但是在所有实际目的中,您可以将它们视为普通类。 - bestsss
4个回答

42

如果您编译类似于枚举的东西

enum Suit {SPADES, HEARTS, CLUBS, DIAMONDS}

您将看到生成的字节码(即javap -p Suit)对应于一个合成类:

final class Suit extends java.lang.Enum<Suit> {
  public static final Suit SPADES;
  public static final Suit HEARTS;
  public static final Suit CLUBS;
  public static final Suit DIAMONDS;
  private static final Suit[] $VALUES;
  public static Suit[] values();
  public static Suit valueOf(java.lang.String);
  private Suit();
}

因此,枚举的实例是类本身的静态成员。我认为唯一可能导致垃圾回收的方式是如果类本身被垃圾回收,但如果它是由系统类加载器加载的话,这种情况是非常不可能发生的。


2
@assylias 我说的是“不太可能”,而文档上说“可能永远不会被最终确定”。我猜想如果类确实被卸载并且你足够幸运,gc运行了,那么你的情况是可能的。 - Edwin Dalorzo
1
老实说,我不知道。 - assylias
1
确实 - 我只是在想为什么枚举类型不会被finalise/GC,因为从字节码的角度来看,它只是一个普通的类。但我不知道确切的答案。 - assylias
2
@assylias - 一个类只有在加载它的类加载器同时被卸载时才会被卸载。因此,除非枚举类型是由自定义类加载器加载的,否则它实际上永远不会被卸载(尽管从概念上讲是可卸载的)。 - Hot Licks
1
@HotLick 我知道这一点 - 但在那种情况下,jls似乎表明枚举仍然不会被垃圾回收,这是我觉得令人惊讶/不确定的部分。 - assylias
显示剩余4条评论

15

垃圾回收器会运行在枚举类型上吗?

只是一个实验,以展示枚举实例可以被标记为垃圾回收对象。

定义一个样本枚举如下 -

public enum SampleEnum { ONE; }

测试代码如下 -

Class<SampleEnum> clazz = SampleEnum.class;
String[] fieldNames = {
    "ONE", "ENUM$VALUES"
};
//create a weak reference to the instance
WeakReference<SampleEnum> ref = new WeakReference<SampleEnum>(SampleEnum.ONE);
//remove the hard references
for (String fieldName: fieldNames) {
    Field feld = clazz.getDeclaredField(fieldName);
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(feld, feld.getModifiers() & ~Modifier.FINAL);
    feld.setAccessible(true);
    feld.set(null, null);
}
//wait until a gc occurs and clears the weak ref
while (ref.get() != null) {
    System.out.println("Waiting ...");
    System.gc();
}
//output just to verify that the weak ref is cleared by the gc
System.out.println("Weak reference is cleared!");

尝试使用-verbose:gc选项。


由于finalizer在垃圾回收运行之前执行,如果没有finalizer是否意味着枚举类型始终保留在内存中,并且垃圾回收器不适用于枚举类型?

实际上,有无finalizer与任何对象是否被垃圾回收没有任何关系。枚举实例不会被垃圾回收,因为它们由编译器生成的枚举类中的静态字段引用。


1
反射可以打破一些语言保证。"使用不安全的反射可以实现XYZ"并没有比"使用sun.misc.Unsafe可以实现XYZ"或者"使用JNI和不安全的指针可以实现XYZ"更有用。 - user253751
1
@immibis:我希望OP从这个答案中得到两个信息-1)没有任何东西告诉GC不要在枚举实例上运行,2)缺少finalizer并不是阻止这些实例被垃圾回收的原因。由于英语不是我的母语,所以我认为我没有正确传达信息。请随意编辑答案。 - Bhesh Gurung
1
不,那很有道理。 - user253751

4

我认为,由于枚举(enum)和枚举类型(enum types)始终是静态(static)的,因此它的行为就像一个静态成员一样,因为可以在任何时间访问它。

此外,finalizers被设计为在对象从内存中卸载时调用,因为它们旨在执行任何必要的清理操作,并且您无法创建枚举(enum)的对象。


枚举不是类。枚举是CTS的5种类型之一。但我同意你对静态的回答(只是不包括静态对象)。 - paparazzo

4

这是一对奇怪的句子。

声明枚举类型不得声明finalizer是编译时错误。

这已经很清楚了。我不知道为什么会这样,但规则就是规则。这似乎也得到了证实,因为枚举派生自Enum,而API规范表明Enum具有一个final finalizer。

枚举类型的实例可能永远不会被终结。

这里的语言对我来说似乎有歧义。他们是否宣称枚举类型的任何实例都不会被终结?如果是这样,为什么用“可能”?为什么不直接说?他们只是提醒我们最终化不能保证发生吗?无论如何,都无关紧要...

Objectfinalize方法被定义为空(12.6)。Enum派生自Object,而枚举派生自EnumEnum的finalizer未被定义做任何事情,并且您无法重写它,因此将所有内容组合起来,枚举实例的最终化没有任何效果。

在我的解释中,枚举实例可以进行垃圾回收(尽管正如其他人指出的那样,允许这种情况发生的情况有点不寻常),并且由于它们的finalize方法被定义为空,无论是否调用finalize方法都没有意义。


2
在所有这些中,人们应该记住Enum在很大程度上是编译器虚构的,并且JVM本身没有任何特别支持Enum的东西。 - Hot Licks
@HotLicks,JVM规范在类文件格式中列出了一些标志来识别枚举,但我不确定为什么或者JVM是否实际上会用到它们。 - Samuel Edwin Ward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接