【问题标题】:Reflection to avoid class load反射以避免类负载
【发布时间】:2023-01-03 06:28:36
【问题描述】:

我正在阅读 PerfMark 代码并看到一条关于通过在提交中使用反射来避免意外类加载的评论:

if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) {
-          Logger.getLogger(PerfMark.class.getName()).log(Level.FINE, "Error during PerfMark.<clinit>", err);
+          // We need to be careful here, as it's easy to accidentally cause a class load.  Logger is loaded
+          // reflectively to avoid accidentally pulling it in.
+          // TODO(carl-mastrangelo): Maybe make this load SLF4J instead?
+          Class<?> logClass = Class.forName("java.util.logging.Logger");
+          Object logger = logClass.getMethod("getLogger", String.class).invoke(null, PerfMark.class.getName());
..
}

不太明白这里防止了哪个类被意外加载。根据Class#forName 将导致加载记录器类。据我了解,只有在封闭条件为真时才会加载该类。或者这是我想念的点?

提交更多上下文在这里: https://github.com/perfmark/perfmark/commit/4f87fb72c2077df6ade958b524d6d217766c9f93#diff-f9fdc8ad347ee9aa7a11a5259d5ab41c81e84c0ff375de17faebe7625cf50fb5R116


我使用 if 块运行该部分,并在 Logger 类的静态和非静态字段上设置断点。无论使用反射还是直接,它仅在执行调用时才命中断点。当 if 条件为假时,无论如何都不会加载记录器。

【问题讨论】:

  • 你只是让你的生活更加困难。 Logger 将在开始时与 JRE 中的所有其他内容一起自动(类)加载。
  • 我怀疑 PerfMark 代码是以这种方式编码的,以使其与不包含 Logger 的旧版 Java 兼容
  • @CtrlAltDel 您的第一条评论具有很强的误导性:JRE 核心中的所有类都是在类路径上可用在开机。他们没有加载。如果每个类都以这种方式初始化和加载,JVM 启动将花费很长时间! - 你的第二个评论是我最好的猜测。
  • @rzwitserloot 抱歉,这听起来对您有很大的误导性。我的意思是你说的“......可用”
  • 我不认为我对“加载”一词的解释是这里的核心问题。无论如何,从稀缺的 cmets 可用(代码本身,以及类似的稀疏提交消息,不幸的是,它没有对此进行扩展,也没有链接到更详细的问题) - 我认为这是不可能的告诉为什么这样做。它当然不是要“避免类负载”,因为它不会。我对该提交发表了评论,要求澄清。

标签: java reflection classloader dynamic-class-creation


【解决方案1】:

我认为该提交的重点是仅在真正需要时才从java.util.logging加载类(当系统属性“io.perfmark.PerfMark.debug”为“true”且err不是null时,即当类 io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl 不可用或该类没有所需的构造函数时。)

如果代码是

Logger.getLogger(PerfMark.class.getName()).log(Level.FINE, "Error during PerfMark.<clinit>", err);

然后java.util.logging.Logger类可以在PerfMark类被验证和链接后立即加载(因为链接PerfMark需要执行静态初始化块)。

使用这个复杂的代码,java.util.logging.Logger 仅在 PerfMark 无法加载其支持类 io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl 时才加载系统属性“io.perfmark.PerfMark.debug”设置为“true”(这可能意味着 java.util.logging.Logger 几乎从不加载只是因为你使用 PerfMark


JVM 规范中有这样的条款,即加载/验证/链接类不需要加载所有引用的类,现代 JVM 实现可能会实现其中的许多要点,以减少不必要的类加载并提高性能。但请记住,PerfMark 作为一个支持从 1.6 到最新版本的 Java 版本的非常通用的库,可能希望防止不必要的类加载,即使 JVM 确实急切地加载引用的类。

这意味着对于非常特殊的图书馆和非常特殊的情况,这是一种非常特殊的技术。如果你要在你的代码中包含类似的技术,我会反对在大多数地方进行这样的更改,质疑这种更改是否真的有必要并得到严格的性能测试的支持。

【讨论】:

  • 您是在谈论 Logger 的静态初始化器的执行吗?这仍然推迟到 Logger.getLogger(…) 的第一次实际调用。另一方面,Logger 类的加载是特定于实现的。在非反射版本中,无论条件的结果如何,验证器都会导致其加载的可能性很高。
  • 我只是在谈论加载docs.oracle.com/javase/specs/jvms/se17/html/… 中指定的类,而不是初始化java.util.logging.Logger。需要加载类来验证 PerfMark.&lt;clinit&gt; 的代码(类验证还可以如何检查 Logger.getLogger().log() 是否有效?)
  • 验证的确切时间是特定于实现的。原则上,可以将 Logger.getLogger().log() 语句的验证推迟到实际执行时进行,这意味着当该语句从未执行时甚至可以跳过验证。实际上,它更复杂,如this Q&A 中所讨论的那样。实际代码的微妙方面可能会改变验证时间(并因此改变依赖项的加载)。
  • 我建议将句子更改为“...java.util.logging.Logger class is loaded as the PerfMark class is验证” 这不仅更正确,而且还暗示了验证者是加载的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-19
  • 1970-01-01
  • 2019-10-29
  • 2021-04-05
  • 1970-01-01
  • 2018-12-14
  • 1970-01-01
相关资源
最近更新 更多