kotlin 单例对象会被 JVM 回收吗?

Sag*_*tus -1 jvm kotlin

kotlin关键字对象创建的单例类会被JVM回收吗?

object MySingleton
Run Code Online (Sandbox Code Playgroud)

如果MySingleton从未使用过,那么在应用程序运行时它是否会继续占用内存?

Sla*_*law 5

前言:这个答案只讨论针对 JVM 的 Kotlin。

\n
\n

如果MySingleton从未使用过,则永远不会加载它,因此永远不会占用任何内存。但是,如果您询问单例在一段时间不使用后(在至少使用过一次之后)是否会被垃圾收集,那么答案基本上是否定的。

\n

假设您有:

\n
object MySingleton\n
Run Code Online (Sandbox Code Playgroud)\n

然后这就是object编译后的内容:

\n
Classfile [omitted]\n  Last modified Mar 9, 2023; size 631 bytes\n  SHA-256 checksum ea5757d5e8673ae4a7153fba4699771529e5db7a28a72c92378c21f6dc7fbc29\n  Compiled from "MySingleton.kt"\npublic final class MySingleton\n  minor version: 0\n  major version: 63\n  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER\n  this_class: #2                          // MySingleton\n  super_class: #4                         // java/lang/Object\n  interfaces: 0, fields: 1, methods: 2, attributes: 2\nConstant pool:\n   #1 = Utf8               MySingleton\n   #2 = Class              #1             // MySingleton\n   #3 = Utf8               java/lang/Object\n   #4 = Class              #3             // java/lang/Object\n   #5 = Utf8               <init>\n   #6 = Utf8               ()V\n   #7 = NameAndType        #5:#6          // "<init>":()V\n   #8 = Methodref          #4.#7          // java/lang/Object."<init>":()V\n   #9 = Utf8               this\n  #10 = Utf8               LMySingleton;\n  #11 = Utf8               <clinit>\n  #12 = Methodref          #2.#7          // MySingleton."<init>":()V\n  #13 = Utf8               INSTANCE\n  #14 = NameAndType        #13:#10        // INSTANCE:LMySingleton;\n  #15 = Fieldref           #2.#14         // MySingleton.INSTANCE:LMySingleton;\n  #16 = Utf8               Lorg/jetbrains/annotations/NotNull;\n  #17 = Utf8               Lkotlin/Metadata;\n  #18 = Utf8               mv\n  #19 = Integer            1\n  #20 = Integer            8\n  #21 = Integer            0\n  #22 = Utf8               k\n  #23 = Utf8               xi\n  #24 = Integer            48\n  #25 = Utf8               d1\n  #26 = Utf8               \\u0000\\f\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0002\\b\xe2\x95\x9e\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0007\\b\\u0002\xc3\xb3\\u0006\\u0002\\u0010\\u0002\xc2\xbf\\u0006\\u0003\n  #27 = Utf8               d2\n  #28 = Utf8\n  #29 = Utf8               [omitted]\n  #30 = Utf8               MySingleton.kt\n  #31 = Utf8               RuntimeInvisibleAnnotations\n  #32 = Utf8               Code\n  #33 = Utf8               LineNumberTable\n  #34 = Utf8               LocalVariableTable\n  #35 = Utf8               SourceFile\n  #36 = Utf8               RuntimeVisibleAnnotations\n{\n  public static final MySingleton INSTANCE;\n    descriptor: LMySingleton;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n    RuntimeInvisibleAnnotations:\n      0: #16()\n        org.jetbrains.annotations.NotNull\n\n  private MySingleton();\n    descriptor: ()V\n    flags: (0x0002) ACC_PRIVATE\n    Code:\n      stack=1, locals=1, args_size=1\n         0: aload_0\n         1: invokespecial #8                  // Method java/lang/Object."<init>":()V\n         4: return\n      LineNumberTable:\n        line 1: 0\n      LocalVariableTable:\n        Start  Length  Slot  Name   Signature\n            0       5     0  this   LMySingleton;\n\n  static {};\n    descriptor: ()V\n    flags: (0x0008) ACC_STATIC\n    Code:\n      stack=2, locals=0, args_size=0\n         0: new           #2                  // class MySingleton\n         3: dup\n         4: invokespecial #12                 // Method "<init>":()V\n         7: putstatic     #15                 // Field INSTANCE:LMySingleton;\n        10: return\n}\nSourceFile: "MySingleton.kt"\nRuntimeVisibleAnnotations:\n  0: #17(#18=[I#19,I#20,I#21],#22=I#19,#23=I#24,#25=[s#26],#27=[s#10,s#28,s#6,s#29])\n    kotlin.Metadata(\n      mv=[1,8,0]\n      k=1\n      xi=48\n      d1=["\\u0000\\f\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0002\\b\xe2\x95\x9e\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0007\\b\\u0002\xc3\xb3\\u0006\\u0002\\u0010\\u0002\xc2\xbf\\u0006\\u0003"]\n      d2=["LMySingleton;","","()V", "[omitted]"]\n    )\n\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,单例的实例保存在public, static,final字段中,并且该字段在类初始化时分配。因此,除非该对象被垃圾回收,否则单例的实例不会被Class垃圾回收。并且Class除非其关联对象被垃圾收集,否则该对象不会ClassLoader被垃圾收集。

\n

ClassLoader但 a被垃圾收集的情况很少见。事实上,我非常有信心它永远不会发生在“标准”应用程序中。请记住,如果类加载器加载的任何类仍在“使用中”,则该类加载器被强引用,因此不符合垃圾回收的条件。

\n

我怀疑只有在管理自己的类加载器的应用程序/框架/容器中,目的是让它们在某些情况下被垃圾收集,你才会看到被ClassLoader垃圾收集。尽管很难确保不存在对按需类加载器的强引用。即使这样,也假设垃圾收集器被允许和/或强制收集类和类加载器请参阅霍尔格的评论

\n

因此,单例实例极有可能在应用程序的整个生命周期中保存在内存中。你应该认为这是确定的。

\n

既然您问过它,这同样适用于顶级变量。如果名为 的源文件中有以下内容Foo.kt

\n
val bar = Any()\n
Run Code Online (Sandbox Code Playgroud)\n

然后这就是它的编译结果:

\n
Classfile [omitted]\n  Last modified Mar 10, 2023; size 629 bytes\n  SHA-256 checksum 35d96f7a51c14816b96102d40d466d14904c19162b4797ef7cbc08333b70394c\n  Compiled from "Foo.kt"\npublic final class FooKt\n  minor version: 0\n  major version: 63\n  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER\n  this_class: #2                          // FooKt\n  super_class: #4                         // java/lang/Object\n  interfaces: 0, fields: 1, methods: 2, attributes: 2\nConstant pool:\n   #1 = Utf8               FooKt\n   #2 = Class              #1             // FooKt\n   #3 = Utf8               java/lang/Object\n   #4 = Class              #3             // java/lang/Object\n   #5 = Utf8               getBar\n   #6 = Utf8               ()Ljava/lang/Object;\n   #7 = Utf8               Lorg/jetbrains/annotations/NotNull;\n   #8 = Utf8               bar\n   #9 = Utf8               Ljava/lang/Object;\n  #10 = NameAndType        #8:#9          // bar:Ljava/lang/Object;\n  #11 = Fieldref           #2.#10         // FooKt.bar:Ljava/lang/Object;\n  #12 = Utf8               <clinit>\n  #13 = Utf8               ()V\n  #14 = Utf8               <init>\n  #15 = NameAndType        #14:#13        // "<init>":()V\n  #16 = Methodref          #4.#15         // java/lang/Object."<init>":()V\n  #17 = Utf8               Lkotlin/Metadata;\n  #18 = Utf8               mv\n  #19 = Integer            1\n  #20 = Integer            8\n  #21 = Integer            0\n  #22 = Utf8               k\n  #23 = Integer            2\n  #24 = Utf8               xi\n  #25 = Integer            48\n  #26 = Utf8               d1\n  #27 = Utf8               \\u0000\\n\\n\\u0000\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0003\\"\\u0011\\u0010\\u0000\\u001a\\u00020\\u0001\xc3\xb3\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\u0002\\u0010\\u0003\xc2\xbf\\u0006\\u0004\n  #28 = Utf8               d2\n  #29 = Utf8\n  #30 = Utf8               [omitted]\n  #31 = Utf8               Foo.kt\n  #32 = Utf8               RuntimeInvisibleAnnotations\n  #33 = Utf8               Code\n  #34 = Utf8               LineNumberTable\n  #35 = Utf8               SourceFile\n  #36 = Utf8               RuntimeVisibleAnnotations\n{\n  private static final java.lang.Object bar;\n    descriptor: Ljava/lang/Object;\n    flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL\n    RuntimeInvisibleAnnotations:\n      0: #7()\n        org.jetbrains.annotations.NotNull\n\n  public static final java.lang.Object getBar();\n    descriptor: ()Ljava/lang/Object;\n    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n    Code:\n      stack=1, locals=0, args_size=0\n         0: getstatic     #11                 // Field bar:Ljava/lang/Object;\n         3: areturn\n      LineNumberTable:\n        line 1: 0\n    RuntimeInvisibleAnnotations:\n      0: #7()\n        org.jetbrains.annotations.NotNull\n\n  static {};\n    descriptor: ()V\n    flags: (0x0008) ACC_STATIC\n    Code:\n      stack=2, locals=0, args_size=0\n         0: new           #4                  // class java/lang/Object\n         3: dup\n         4: invokespecial #16                 // Method java/lang/Object."<init>":()V\n         7: putstatic     #11                 // Field bar:Ljava/lang/Object;\n        10: return\n      LineNumberTable:\n        line 1: 0\n}\nSourceFile: "Foo.kt"\nRuntimeVisibleAnnotations:\n  0: #17(#18=[I#19,I#20,I#21],#22=I#23,#24=I#25,#26=[s#27],#28=[s#8,s#29,s#5,s#6,s#30])\n    kotlin.Metadata(\n      mv=[1,8,0]\n      k=2\n      xi=48\n      d1=["\\u0000\\n\\n\\u0000\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0003\\"\\u0011\\u0010\\u0000\\u001a\\u00020\\u0001\xc3\xb3\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\u0002\\u0010\\u0003\xc2\xbf\\u0006\\u0004"]\n      d2=["bar","","getBar","()Ljava/lang/Object;","[omitted]"]\n    )\n\n
Run Code Online (Sandbox Code Playgroud)\n

顶级变量被编译为名为 的类的private, static,字段,并且该字段在类初始化时被赋值。由于在本例中该字段是私有的,因此您会注意到有一个,finalFooKtpublicstatic getter 方法可以访问它。由于该字段是静态的,因此与上面关于何时以及是否进行垃圾收集的推理相同。

\n

  • 你的怀疑是对的。该规范甚至明确提到“[引导加载程序加载的类和接口可能不会被卸载。](https://docs.oracle.com/javase/specs/jls/se17/html/jls-12.html#jls -12.7:~:text=Classes%20and%20interfaces%20loaded%20by%20the%20bootstrap%20loader%20may%20not%20be%20unloaded.)”作为您描述的规则的结果。由于可以从引导加载程序(`ClassLoader.getSystemClassLoader()`)加载的类访问应用程序类加载器,因此这适用于通过类路径或模块路径加载的所有“普通”应用程序类 (3认同)