JDK 16中的JVM远程监视调试方法 - egahlin


长期以来,应用程序监视工具已经能够使用JMX通过网络连续获取数据。例如,可以从OperatingSystemMXBean获得CPU负载,并在JDK Mission Control中将其可视化。
JDK16的JFR提供了更丰富的结构化数据,例如堆栈跟踪和带有时间戳的值,可通过网络传输这些信息。
 
JDK14
在JDK 14中,对流事件添加了API支持,如以下代码段所示:


1. Passive, in process:
try (EventStream stream = EventStream.openRepository()) {
    stream.onEvent("jdk.JavaMonitorEnter", System.out::println),
    stream.start();
}
2. Passive, out of process:
Path path = Path.of(
"/repository/2021_05_16_09_48_31_60185");
try (EventStream stream = EventStream.openRepository(path) {
    stream.onEvent(
"jdk.JavaMonitorEnter", System.out::println),
    stream.start();
}
3. Active, in process:
try (RecordingStream stream = new RecordingStream()) {
    stream.enable(
"jdk.JavaMonitorEnter").withStackTrace();
    stream.onEvent(
"jdk.JavaMonitorEnter", System.out::println),
    stream.start();
}

这里代码意味着可以通过流Stream来控制记录生命周期和事件配置。
上面的API在许多情况下都适用,但是在以下情况时则无效:
  • 监视远程主机上的Java进程。
  • 控制正在远程主机上和/或用于另一个进程的正在记录的内容。

从JDK 11开始,OpenJDK中存在一个FlightRecordingMXBean,可以远程控制和下载记录。这就是JDK Mission Control如何获取记录数据并在远程计算机上配置事件的方式。在JDK 15和早期版本中,必须先停止记录,然后客户端才能读取数据。
 
JDK16
在JDK 16中,取消了此限制,并且现在可以使用MFR使用MBeanServerConnection来监视JFR :
String host = "com.example";
int port = 7091;
 
String url =
"service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi";
 
JMXServiceURL u = new JMXServiceURL(url);
JMXConnector c = JMXConnectorFactory.connect(u);
MBeanServerConnection connection = c.getMBeanServerConnection();

try (RemoteRecordingStream stream = new RemoteRecordingStream(connection)) {
    stream.enabled(
"jdk.JavaMonitorEnter").withStackTrace();
    stream.onEvent(
"jdk.JavaMonitorEnter", System.out::println),
    stream.start();
}

RemoteRecordingStream的实现从FlightRecorderMXBean :: readStream(long)方法读取字节数据,并将其分块写入本地磁盘,类似于JVM在远程主机上所做的操作。然后,另一个线程解析磁盘上的数据,并将事件调度到onEvent处理程序。每秒一次,可以读取新数据。
为了确保解析器线程在数据段完成之前不会读取数据,块头中有一个size字段,该字段指示可以读取文件数据的深度。一旦新数据到达并且分段完成,该字段将被更新。为了确保在读取时不会修改size字段,解析器必须遵循一种协议以避免字撕裂。

读取块文件并调度其事件后,将从客户端中删除该文件。要保留数据,可以设置两个策略setMaxAge(Duration)和setMaxSize(long),以确定应保留多长时间的数据。
RemoteRecordingStream类不仅用于流事件,还用于将磁盘存储库迁移到另一台主机。如果受监视的应用程序崩溃,并且主机上的磁盘存储库文件已删除,则它们在RemoteRecordingStream操作的计算机上仍然可用。计划是向RemoteRecordingStream添加转储方法,以便在出现问题时可以轻松地提取文件。
 
流事件元数据
但是还是缺少对事件元数据的访问,在进行流式处理时,可以调用FlightRecorder :: getEventTypes()方法以获取所有已注册事件类型的列表。如果没有事件类型的知识,就无法确定字段布局或可以启用/配置的事件。
为了解决这种情况,在EventStream接口中添加了一个新方法:

void onMetadata(MetadataEvent);

MetadataEvent携带所有已注册的事件类型和两个配置,“默认”和“配置文件”列表中,都是来自自带的JDK。在调用任何onEvent处理程序之前,将发送MetadataEvent。如果新的事件类型已注册/未注册,则发送更新的MetadataEvent。
要查看运行中的RemoteRecordingStream类,有一个名为Health Report.java的小型单文件程序,该程序订阅事件并将其打印为标准输出。
用法:
$ java HealthReport.java com.example:7091