JDK飞行记录仪(JFR)是Java的秘密武器之一;它深入集成到Hotspot VM中,是一个高性能的事件收集框架,可用于收集运行时方面的指标,如对象分配和垃圾收集、类加载、文件和网络I/O、锁争用、执行方法分析等。
JFR数据保存在记录文件中(因为Java 14也支持实时事件流),可以将其加载到JDK任务控制(JMC)或OpenJDK自带的JFR实用程序中进行分析。
虽然有很多关于JFR本身的博客帖子、会议演讲和其他报道,但令人惊讶的是,关于记录文件格式的信息却层出不穷。没有正式的规范,所以真正理解JFR文件格式的唯一方法是读取源代码,用于编写JDK本身的录音,这是java和C++代码的组合。或者,您可以在JMC(一个正式的JDK项目)中学习解析记录的代码。顺便说一句,JMC还提供了一个纯基于Java的JFR文件编写器实现。
除了源代码本身,唯一其他一些相关的资源是Marcus Hirt的几篇与JFR相关的博客文章,以及Richard Startin的一篇关于JFR事件大小的文章。但对文件格式没有深入的讨论或解释。事实证明,这一切都是有意为之;OpenJDK团队回避创建规范&34;因为维护和保持兼容的开销";。也就是说,JFR文件格式是OpenJDK的一个实现细节,因此与之交互的唯一稳定契约是JFR提供的API。
现在,如果这是一个实现细节,了解更多关于JFR文件格式的信息肯定会很有用;例如,您可以使用它来实现工具,用非JVM编程语言(比如Python)分析和可视化JFR数据,或者修补损坏的记录文件。所以我的好奇心被激发了,我想尝试了解JFR录音文件是如何构造的会很有趣。特别是,我想知道哪些技术用于保持文件相对较小,以及数十万甚至数百万记录的事件。
我抓取了一个十六进制编辑器,这是JMC录音解析器的源代码(我发现它比JDK中的Java/C++混合版本更容易掌握),并从我的JFR Analytics项目中加载了几个示例录音,在调试模式下逐步分析解析器代码(有趣的事实:在这样做时,我注意到JMC目前无法解析具有char属性的事件)。
仅仅几个小时后,我基本上理解了它的工作原理。正如一张图片所说的千言万语,我永远不会拒绝在神奇的Excalidraw中绘制某些东西的机会,因此,根据我的理解,我自豪地向大家展示JFR文件格式的可视化(点击放大):
最好在大屏幕上观看😎.或者,这里有一个SVG版本。现在,这并没有涉及到所有最好的方面,所以您可能无法单独基于此实现一个干净的JFR文件解析器。但它确实展示了相关的概念和机制。我建议您花一些时间浏览图片中的第一到第五部分,然后深入了解标题、元数据、常量池和实际记录的事件。研究图像可以让你很好地理解JFR文件格式及其结构。
以下是我在浏览文件格式时所做的一些观察:
JFR记录被组织成块:块是记录的事件以及解释这些事件所需的所有元数据的独立容器。除了区块之外,录制中没有其他内容,例如,concat几个区块文件,您将拥有一个JFR录制文件。使用OpenJDK附带的jfr实用程序,可以将多块记录文件拆分为各个块:
默认chunksize为12MB,但如果需要,您可以覆盖它,例如在开始录制时使用-XX:FlightRecorderOptions:maxchunksize=1MB选项。例如,如果您只想传输长时间运行的录音的特定部分,则较小的数据块大小会很有用。另一方面,由于重复存储元数据和常量池,许多小数据块会增加记录的整体大小
事件格式是自描述性的:每个块的元数据部分描述所包含事件的结构、所有引用的类型及其属性等。;通过JFR元数据注释,如@Label、@Description、@Timestamp等,进一步的元数据,如人类可读的名称和描述以及度量单位被表达出来,允许在不预先知道特定事件类型的情况下使用和解析事件流。特别是,这允许定义自定义事件类型,并将其显示在JMC的通用事件浏览器中(当然,诸如";内存";视图等定制视图依赖于单个事件类型的特定类型解释)
该格式旨在提高空间效率:整数值以可变长度编码方式(LEB128)存储,这将在存储小数值时保护大量空间。常量池用于存储重复引用的对象,如字符串文字、堆栈跟踪、类和方法名等。;对于记录的事件中每次使用此类常量,只存储常量池索引(var长度编码为long)。请注意,字符串可以作为原始值存储在事件本身中,也可以存储在常量池中。不幸的是,没有提供在两者之间进行选择的控制;长度在16到128之间的字符串将存储在常量池中,其他字符串将作为原始值存储。这可能是一个很好的扩展,可以让事件作者在这里拥有更多的控制权,例如通过事件属性定义上的注释
当使用jdk时。OldObjectSample事件类型,请注意错误JDK-8277919,这可能会导致常量池膨胀,因为同一个条目在池中重复多次。这将在Java 17.0.3和18中修复。
该格式基于行:事件在记录文件中顺序存储;这意味着,例如,布尔属性将消耗一个完整字节,如果一个字节中可以存储八个布尔值的话。探索柱状格式作为替代方案可能会很有趣,这可能有助于进一步减少记录大小,例如,还允许使用增量编码有效地压缩事件时间戳值
JMC阅读器实现中的压缩支持:JMC的JFR解析器实现透明地解压使用GZip、ZIP或LZ4压缩的记录文件(Marcus Hirt在本文中讨论了JFR记录的压缩)。有趣的是,JMC 8.1仍然无法打开这样的压缩记录,并显示错误消息。jfr实用程序不支持压缩录音文件,我想JDK中的jfr编写器也不会生成压缩录音