I define custom JFR events like this:
@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
}
When using the event the code does this:
val event = if (hasJFR) {
Some(new PerfScopeEvent(scopeName))
} else {
None
}
event.foreach(_.begin())
// ....
event.foreach { e =>
e.end()
e.commit()
}
The purpose of the code is to skip the PerfScopeEvent
class functionality when running on Java 8, where class jdk.jfr.Event
is not available.
I have verified in the debugger the hasJFR
is constructed fine, when running on Java 8 it is false because an exception NoClassDefFoundError
is thrown while calling FlightRecorder.register(classOf[PerfScopeEvent])
.
The trouble is that in the "using the event part" I get an exception as well. The exception is thrown when event.foreach(_.begin())
is executed. This is confusing me.
The exception is:
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
I can prevent the exception by using if (event.isDefined) event.foreach(_.begin())
instead (and the same for the second use).
What difference does wrapping in the if (event.isDefined)
have? Why does classloader load the class jdk/jfr/Event
when using foreach
directly?
I was able to create a minimal code which still shows the same issue:
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) => ()
}
}
The bytecode generated from this is:
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$1(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$1$adapted(OptionalJFR$PerfScopeEvent);
Code:
0: aload_0
1: invokestatic #58 // Method $anonfun$main$1:(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
}
With foreach
, the lambda (an instance of Function[PerfScopeEvent, Unit]
in this case) is constructed, and then foreach
is called. In the case of None
, foreach
doesn't execute the lambda.
This means that side effects of constructing the lambda will happen, even if the lambda is never executed. Classloading is such a side effect.
By wrapping the call to foreach
in an if
, the lambda is not created until after the condition check.
That said, once you're doing an isDefined
check, it's probably worth considering unwrapping the Some
and not using map
/flatMap
/foreach
and friends (it will be more performant by not constructing lambdas, though there are valid reasons to maintain an absolute style). Also note that eq None
is almost certainly faster than isDefined
.