【问题标题】:When is a Java Class loaded?什么时候加载 Java 类?
【发布时间】:2015-12-14 03:21:21
【问题描述】:

我在互联网上搜索了两个多小时,但没有得出任何结论。

最近我决定将 BouncyCastle 用于 SSL,但我希望它默认关闭,因此 BouncyCastle jar 可能不在类路径中。

private void enableBouncyCastleForSSL() {
   if (config.isBouncyCastleEnabled()) {
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
    }
} 

即使禁用了配置,它也在寻找 BouncyCastle,但由于类加载器错误而失败。 java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider

我尝试只移动一行 Security.insertProviderAt(new BouncyCastleProvider(), 1);换成一种新方法,它表现出同样的问题。

但是当我引入一个类并在其中移动 BouncyCastle 时,当配置被禁用时,不会出现类加载器问题

private void setupSSLProvider() {
    if (voldemortConfig.isBouncyCastleEnabled()) {
        SetupSSLProvider.useBouncyCastle();
    }
}
public class SetupSSLProvider {
  public static void useBouncyCastle() {
    Security.insertProviderAt(new BouncyCastleProvider(), 1);
  }
}

有些文章声称 Class 仅在第一次使用时才加载。 http://www.programcreek.com/2013/01/when-and-how-a-java-class-is-loaded-and-initialized/

显然在我的例子中,Java8 加载了一个类中引用的类。

所以我的理解是 Java 会在执行类中的第一行代码之前将类加载到一个深度。是这样吗?

【问题讨论】:

  • 您是否考虑过查阅 Java 语言规范和 Java 虚拟机规范?而不仅仅是任意的互联网垃圾?您的引文似乎有 14 年的历史,并且依赖于实证调查,而不是官方规范。事实上,引用的 IBM 文档明确指出“基本原则是仅在需要时加载类(或至少看起来以这种方式加载——JVM 在实际加载中具有一定的灵活性,但必须保持类的固定顺序)初始化)”,这应该是对作者的警告。
  • 当然这是一个选项,但我认为这是大多数人应该知道的一些标准。有大量关于类加载的文章(3 种类型的类加载器,但没有任何细节)。阅读规范似乎需要一两天的时间,而且语言不是为外行编写的。如果我以不同的方式构建我的问题,何时加载一个类,可以有 3 个答案,引用它的类/方法被加载或何时被访问。如果我必须阅读规范才能得到这个答案,我会感到惊讶。

标签: java java-8 classloader openjdk


【解决方案1】:

这个问题没有简单的答案。该规范为不同的实现策略留出了空间,即使在一个实现中,它也将取决于如何使用该类。此外,您的问题更多是关于“它什么时候会失败”,这取决于它为什么会失败,例如一个类可能在某个时间点加载以验证它的存在,但在稍后实际第一次使用它时初始化。在这两个时间点,操作都可能失败,导​​致NoClassDefFoundError

我们可以将引用的类分为三组:

  • 必须在链接时解析的类(例如超类)
  • 验证时必须加载的类
  • 可以推迟到第一次实际使用时加载的类

在您的情况下,BouncyCastleProvider 必须在验证时加载,而不是第一次实际使用,因为您将 new BouncyCastleProvider() 的结果作为参数传递给方法 Security.insertProviderAt(…) 和验证者必须检查该类是否实际上扩展了方法的形参要求的Provider 类型。

何时进行验证,也是特定于实现的,因为至少允许以下可能性:

  • 急切地遍历所有引用的类
  • 在加载包含类或首次使用时
  • 关于包含方法的首次使用
  • 就在执行违规指令之前

Oracle 的 JVM 更喜欢方法的第一次使用,阅读:在方法入口处验证它,因此,将调用移动到另一个不会被执行的方法中,无论它是否在另一个类中,在你的情况下就足够了。

但为了与其他 JVM 兼容,将其移至另一个类更安全,但为了符合所有可能的 JVM,您甚至需要通过反射加载这个其他类以避免直接引用可以通过一个 Eager 实现遍历(或者首先通过 Class.forName("… .BouncyCastleProvider").newInstance() 反射地实例化 BouncyCastleProvider)。

【讨论】:

  • 看来我遇到了同样的事情:(。你是说如果我有一个像interface CustomAction extends Action这样的声明和一个方法public void go(Action action)bootstrap正在加载Action.class,当另一个类加载器试图调用 go 时,这会失败吗?在我的情况下,我得到一个 LinkageError 并且我认为我现在明白为什么了,但不能真正判断这是否可以解决...
  • @Eugene 通常,类加载的顺序无关紧要。在这里,有一个缺席类的特殊情况,它可以产生影响。您的描述不包含这种情况。
  • 同意。我真的希望有时间对此提出适当的问题,但最后LinkageError 来自MethodHandles 的使用,它将创建某个方法的句柄(将接口作为参数)。很奇怪,当我改为纯反射时(我猜与验证器有关),链接错误消失了。我讨厌我真的不明白为什么:(
猜你喜欢
  • 1970-01-01
  • 2011-11-25
  • 1970-01-01
  • 2021-01-19
  • 1970-01-01
  • 2013-02-27
  • 2018-10-24
  • 2019-06-25
  • 1970-01-01
相关资源
最近更新 更多