【问题标题】:Can static variable be initialized many times, because of class unloading?由于类卸载,静态变量可以多次初始化吗?
【发布时间】:2021-03-21 08:53:19
【问题描述】:

大家好,当我观看有关 G1GC 的介绍时,我了解到,这个 GC 可以在收集垃圾期间卸载类。让我们想象一下,我们有这样一个类。

public class Foo {
   public static int a = 5;

}

假设我们的代码中没有对这个类的任何引用,以便让 GC 清楚地知道这个类将不再被使用。为了使类加载和初始化发生,我们定期访问这个int a 变量。为了在代码中没有任何引用,我们通过反射来访问这个变量,在http请求调用的方法中,以类名作为参数。

在这种情况下,或任何类似情况下,是否有可能多次初始化类,这也意味着静态字段将被初始化多次?

静态字段可以初始化几次吗?

【问题讨论】:

  • 静态字段未多次初始化:正在创建类的新实例,其中包含静态字段值的新实例。在您的示例中,您确实有对该类的引用,即使您只是通过加载它的类加载器反射性地访问它。
  • @Holger 你能把这个评论作为答案吗?因为它恰到好处,我想标记它。如果您能详细说明,我将不胜感激,可能的情况是我们在一个应用程序中有多个类加载器? (不是一个 JVM)为什么这个特性(类卸载)有用?仅在重新部署应用程序时,对已销毁应用程序的垃圾类加载器,而不重新启动应用程序服务器?

标签: java garbage-collection classloader g1gc


【解决方案1】:

这在 §12.7. Unloading of Classes and Interfaces 的规范中得到了解决:

Java 编程语言的实现可以卸载类。

当且仅当垃圾收集器可以回收其定义的类加载器(如§12.6 中所述)时,才能卸载类或接口。

引导加载程序加载的类和接口可能不会被卸载。

规则的动机与您的问题直接相关:

类卸载是一种有助于减少内存使用的优化。显然,程序的语义不应该取决于系统是否以及如何选择实现诸如类卸载之类的优化。否则会损害程序的可移植性。因此,一个类或接口是否被卸载应该对程序是透明的。

如果类有 static 变量(其状态将丢失)、静态初始化器(可能有副作用)或 native 方法(可能保留静态)。此外,Class 对象的哈希值取决于其身份。因此,通常不可能以完全透明的方式重新加载类或接口。

由于我们永远不能保证卸载一个加载器可能可达的类或接口不会导致重新加载,并且重新加载永远不是透明的,但卸载必须是透明的,因此不能卸载一个类或接口而它的加载器可能是可达的。类似的推理可以用来推断引导加载程序加载的类和接口永远不会被卸载。

除了明确提到的引导加载程序(它需要一个明确的声明,因为它由null 表示,因此可达性没有意义),应用程序类加载器,也称为system class loader 始终是可达的。这同样适用于它的父级,JDK 9 之前的扩展类加载器和之后的 platform class loader

这意味着对于由应用程序加载器加载的普通应用程序及其所有直接依赖项,类卸载根本不可能。只有应用程序或框架创建的其他类加载器可能无法访问。

上面引用的规范还说:

类卸载是一种优化,仅对加载大量类并在一段时间后停止使用其中大部分类的应用程序有意义。此类应用程序的一个主要示例是 Web 浏览器,但还有其他应用程序。此类应用程序的一个特点是它们通过显式使用类加载器来管理类。因此,上述政策对他们很有效。

这个例子反映了 Java 的历史,但今天,一个主要的例子宁愿是 application server。在重新部署新版本时,甚至在使用新的类加载器再次加载相同的类文件时,它们在技术上是不同的类,并且卸载的透明度无关紧要。

虚拟机规范§5.3. Creation and Loading 对此进行了说明,其中指出:

在运行时,类或接口不仅仅由其名称决定,而是由一对:其二进制名称 (§4.2.1) 及其定义类加载器。


一个特例是通过新的defineHiddenClass method 创建的类。由于这些类无法通过其定义的加载器按名称解析,因此可以在不存在对隐藏类的引用时立即卸载它。它无法再次重新加载。但是您可以使用相同的定义来创建任意数量的相同隐藏类,因为它们不会相互干扰。

【讨论】:

  • 太好了,这个详尽的答案和您的第一条评论,完美地解决了我提出的所有问号。非常感谢。
【解决方案2】:

您是否尝试过使用 singleton 设计模式创建持久对象?

单例是在整个应用程序中唯一的对象类,在第一次实例化时创建

只要应用程序正在运行,单例就存在,并且任何实例化都会返回当前实例,如果之前没有实例化,则返回新实例。 如果您想要一个新实例,您可以在单例类上实现一个方法,该方法会破坏当前实例并返回一个新实例。 我使用单例来存储设置以及访问和凭据管理。

这是关于单身人士的帖子的链接: Singleton Design Pattern

如果你不运行多个线程,你可以使用帖子中描述的方法1,这是我经常做的。 但是,如果您运行多个线程,我建议您使用帖子中提到的最后一种方法,因为它声称是最好的,尽管我还没有测试过。

【讨论】:

    猜你喜欢
    • 2021-05-02
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 2010-12-22
    相关资源
    最近更新 更多