JFR 事件流停止 - Sip of Java

JDK 20 的发布为 JDK Flight Recorder (JFR) 带来了多项更新。最重大的变化是针对事件流 API。我们来看看这些变化以及如何在应用程序中使用它们。

新的 JFR 事件

与每次 JDK 发布一样,都会添加一些新的 JFR 事件,而 JDK 20 也不例外。添加了三个新的 JFR 事件,使预定义事件的总数达到 177。添加的事件为

  • jdk.InitialSecurityProperty (JDK-8292177)
  • jdk.SecurirtyProviderService (JDK-8294239)
  • jdk.JITRestart (JDK-8289524)

stop() 已添加到 JFR 事件流 API

JFR 事件流 API 在 JDK 14 中添加,随附 JEP 349。JFR 事件流 API 提供多项有价值的功能:对 JFR 事件的编程访问、应用程序和 JVM 内部近乎实时的视图,以及在自动化测试中使用 JFR 的能力。新 .stop() 方法将在最后一个项目中看到特别的优势。要了解 .stop() 的优势,让我们先回顾一下如何在 JDK 19 及更早版本中使用 JFR 进行自动化测试。

在 JDK 20 之前进行自动化测试中的 JFR

虽然 JFR 可以用于自动化测试,但它并不是非常直接,而且会产生很多视觉噪音,如下面的示例所示

SampleApplication sampleApplication = new SampleApplication();

@Name("TestEvent") class TestEvent extends Event {}

@Test
public void testDoStuff() throws Exception {
	List<RecordedEvent> recordedEvents = new ArrayList<>();
	try (RecordingStream rs = new RecordingStream();) {
		rs.onEvent("TestEvent", e -> rs.close());
		rs.onEvent("MyEvent", recordedEvents::add);
		rs.startAsync();
		TestEvent testEvent = new TestEvent();
		sampleApplication.doStuff();
		testEvent.commit();
		rs.awaitTermination();
	}
	
	for (RecordedEvent recordedEvent : recordedEvents) {
		if(recordedEvent.getValue("value").equals("testValue")) {
			return;
		}
	}
	fail("A MyEvent event wasn't found!");
}

虽然在自动化测试中使用 JFR 有很多不同的途径,但上面的示例在最大程度缩短测试执行时间的同时,确保捕获开发人员正在寻找的 JFR 事件,是最有效的途径之一。不过,此示例会产生很多视觉噪音,并且不直接。

信号事件

其中一个最令人烦恼的部分是开发人员需要创建一个“信号”事件,用作测试中关闭流的信号。在上面的示例中,这是通过 TestEvent 完成的。此事件的唯一目的是在被测试的代码执行完毕时发出信号,这可以通过 testEvent.commit(); 行在被测试的方法之后看到;sampleApplication.doStuff();

关闭流

信号事件 TestEvent 的另一个目的是触发关闭 JFR 流,这通过 rs.onEvent("TestEvent", e -> rs.close()); 行完成。这本质上是在告诉流,当 TestEvent 添加到流时关闭。

暂停执行

需要发出信号事件并关闭流,因为事件不会在发生时立即写入 JFR 事件流中。这就是我之前使用“接近实时”这个词组的原因,因为事件大约每秒才会刷新到流中一次。但是,这并不能保证,因为 JFR 事件流可能会因等待系统资源或只是有许多事件要写入而被阻塞。因此,一个简单的 Thread.sleep() 无法可靠地工作。相反,rs.awaitTermination(); 用于暂停测试执行,直到流被 rs.onEvent("TestEvent", e -> rs.close()); 关闭。

实际上,这通常会使测试执行时间增加大约一秒。单独来看,这并不意味着什么,但如果在许多自动化测试中使用了 JFR,那么这些额外的几秒钟会显著增加测试套件的执行时间。

幸运的是,使用 stop(),这一切都已成为过去。

使用 .stop() 的极大简化

使用 .stop() 编写的与上述示例等效的自动化测试如下所示

SampleApplication sampleApplication = new SampleApplication();

@Test
public void testDoStuff() throws Exception {
	List<RecordedEvent> recordedEvents = new ArrayList<>();
	try (RecordingStream rs = new RecordingStream();) {
		rs.onEvent("MyEvent", recordedEvents::add);
		rs.startAsync();
		sampleApplication.doStuff("testValue");
		rs.stop();
	}
	//Assert here
	for (RecordedEvent recordedEvent : recordedEvents) {
		if(recordedEvent.getValue("captureArgument").equals("testValue")) {
			return;
		}
	}
	fail("A MyEvent event wasn't found!");
}

除了极大简化测试编写、无需发出关闭 JFR 流的信号事件以及暂停执行以等待流关闭的明显好处之外,测试执行速度也大大提高了。当调用 rs.stop() 时,这会立即将所有事件刷新到流中并关闭流。测试不再需要暂停大约一秒钟来等待事件刷新。

延伸阅读

JEP 349:JFR 事件流

录制流 .stop()

JFR 单元

InitialSecurityProperty JFR 事件(JBS 问题)

发行说明:新 JFR 事件:jdk.SecurityProviderService(JBS 问题)

添加 JFR JIT 重启事件(JBS 问题)

编码愉快!