当使用 foreach 时,类加载器为 None 加载 class

Classloader loading a class for None when foreach is used on it

我这样定义自定义 JFR 事件:

  @Name("xxxx.jfr.PerfScope")
  @Label("PerfScope scope")
  @Category(Array("Java Application"))
  @Description("Scope with a tracked performance")
  @StackTrace(false)
  class PerfScopeEvent(
    name: String
  ) extends jdk.jfr.Event

  val hasJFR = try {
    FlightRecorder.register(classOf[PerfScopeEvent])
    true
  } catch {
    case _: NoClassDefFoundError =>
      // this is expected when running on JDK 8
      false
  }

使用事件时,代码会这样做:

    val event = if (hasJFR) {
      Some(new PerfScopeEvent(scopeName))
    } else {
      None
    }

    event.foreach(_.begin())

    // ....

    event.foreach { e =>
      e.end()
      e.commit()
    }

代码的目的是在 Java 8 上 运行 时跳过 PerfScopeEvent class 功能,其中 class jdk.jfr.Event不可用。

我已经在调试器中验证了 hasJFR 构造良好,当 运行 在 Java 8 上时它是错误的,因为调用 NoClassDefFoundError 时抛出异常 FlightRecorder.register(classOf[PerfScopeEvent]).

问题是在“使用事件部分”中我也遇到了异常。执行 event.foreach(_.begin()) 时抛出异常。这让我很困惑。

例外情况是:

Exception in thread "main" java.lang.BootstrapMethodError: java.lang.NoClassDefFoundError: jdk/jfr/Event
Caused by: java.lang.NoClassDefFoundError: jdk/jfr/Event
Caused by: java.lang.ClassNotFoundException: jdk.jfr.Event
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 22 more

我可以通过使用 if (event.isDefined) event.foreach(_.begin()) 来防止异常(第二次使用也是如此)。

if (event.isDefined) 中的换行有什么区别?为什么直接使用foreach时classloader会加载classjdk/jfr/Event

我能够创建一个仍然显示相同问题的最小代码:

import jdk.jfr._

object OptionalJFR  {

  @Name("com.github.ondrejspanel.jfr.PerfScope")
  @Label("PerfScope scope")
  @Category(Array("Java Application"))
  @Description("Scope with a tracked performance")
  @StackTrace(false)
  class PerfScopeEvent(@Label("name") val name: String) extends Event

  def main(args: Array[String]): Unit = {
    val s = (s: PerfScopeEvent) => ()
  }
}

由此生成的字节码为:

public final class OptionalJFR$ {
  public static final OptionalJFR$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class OptionalJFR$
       3: dup
       4: invokespecial #22                 // Method "<init>":()V
       7: putstatic     #24                 // Field MODULE$:LOptionalJFR$;
      10: return

  public void main(java.lang.String[]);
    Code:
       0: invokedynamic #48,  0             // InvokeDynamic #0:apply:()Lscala/Function1;
       5: astore_2
       6: return

  public static final void $anonfun$main(OptionalJFR$PerfScopeEvent);
    Code:
       0: return

  private OptionalJFR$();
    Code:
       0: aload_0
       1: invokespecial #56                 // Method java/lang/Object."<init>":()V
       4: return

  public static final java.lang.Object $anonfun$main$adapted(OptionalJFR$PerfScopeEvent);
    Code:
       0: aload_0
       1: invokestatic  #58                 // Method $anonfun$main:(LOptionalJFR$PerfScopeEvent;)V
       4: getstatic     #64                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
       7: areturn

  private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
    Code:
       0: aload_0
       1: invokedynamic #76,  0             // InvokeDynamic #1:lambdaDeserialize:(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;
       6: areturn
}

foreach构造lambda(本例中是Function[PerfScopeEvent, Unit]的一个实例),然后调用foreach。在 None 的情况下,foreach 不执行 lambda。

这意味着构建 lambda 的副作用将会发生,即使 lambda 从未执行过。类加载就是这样的副作用。

通过将对 foreach 的调用包装在 if 中,直到条件检查后才会创建 lambda。

就是说,一旦您进行了 isDefined 检查,可能值得考虑展开 Some 而不使用 map/flatMap/foreach 和朋友们(不构造 lambda 会提高性能,尽管有充分的理由保持绝对风格)。另请注意 eq None 几乎肯定比 isDefined.