使用foreach触发jdk.jfr.Event类加载异常的原因及if判断的作用
Great question! Let's unpack why these two approaches behave so differently, and why the direct foreach triggers that annoying NoClassDefFoundError while the if check avoids it.
First, the Core Difference
The key boils down to when the Java Virtual Machine (JVM) tries to load and resolve classes/methods.
When you call event.foreach(_.begin()) directly:
- The compiler generates bytecode that invokes the
begin()method on yourPerfScopeEventinstance. For the JVM to execute thisinvokevirtualinstruction, it needs to resolve the method's defining class—including yourPerfScopeEventand its parentjdk.jfr.Event. Even if yourhasJFRflag isfalse(for Java 8), the JVM tries to resolve this method during the class loading phase before checking runtime flags. Since Java 8 doesn't havejdk.jfr.Event, this resolution fails withNoClassDefFoundError.
When you wrap the call in if(event.isDefined):
- The
event.isDefinedcheck acts as a guard that tells the JVM the code inside the block will never run in Java 8 environments. The JVM uses lazy resolution: it won't attempt to resolve thebegin()method call unless the condition is true. Since the condition is false in Java 8, the JVM skips parsing and resolving that code entirely—so it never tries to loadjdk.jfr.Event.
Why Direct foreach Triggers Class Loading
Java's class loading follows strict rules for "active use" of a class. Calling an instance method of a class counts as active use, which forces the JVM to load the class (and its parent classes) and resolve the method.
Even if you have a static flag like hasJFR that logically should skip the code, the JVM doesn't care about that flag during the initial class resolution phase. It sees the method call in the bytecode, tries to resolve it, and hits the missing jdk.jfr.Event class before your runtime flag can do anything.
Breaking It Down With Your Code & Bytecode
Let's use your minimal example to make this concrete. Suppose your code looks like this:
public class JfrTest { private static final boolean hasJFR = false; // Java 8 environment private static List<PerfScopeEvent> events = new ArrayList<>(); public void directCall() { events.forEach(PerfScopeEvent::begin); // Throws NoClassDefFoundError } public void guardedCall() { if (hasJFR) { events.forEach(PerfScopeEvent::begin); // No error } } } class PerfScopeEvent extends jdk.jfr.Event {}
Looking at the bytecode for directCall():
You'll see an invokevirtual instruction targeting PerfScopeEvent.begin(). The JVM tries to resolve this method when loading JfrTest, which requires loading jdk.jfr.Event—boom, error.
For guardedCall():
The bytecode includes a conditional jump (ifeq) that skips the foreach block when hasJFR is false. Since the JVM knows this branch won't execute in Java 8, it doesn't waste time resolving the begin() method call inside the block. No class loading attempt = no error.
One More Thing: How event.isDefined Works
That isDefined check is likely using reflection (like Class.forName("jdk.jfr.Event", false, getClass().getClassLoader())) to safely check if the JFR class exists without triggering full class loading. It returns false in Java 8, so the guard block stays unexecuted.
内容的提问来源于stack exchange,提问作者Suma

