使用jfr让java观测更简单

背景

随着jdk的发展,对Java问题诊断的方式越来越便捷,在openjdk11上开源了javaFlightRecording(后面简称jfr),这个框架提供了一种能力把jdk运行的内部状态记录下来,在需要的时候拿到文件进行分析,以此了解到了运行时的jvm状态,这个功能在排查问题方便特别方便,在社区的努力下backport回到了jdk8。

jfr提供除了提供已经定制好的事件,还提供了扩展的能力,用户可以自己增加事件来做扩展。

jfr事件

了解jfr事件需要从2个维度入手。

第一个维度是收集的类型。分为以下三种。

  1. instant event。就是发生的时候就立刻记录,至于记录到什么级别后面讲。
  2. duration event。是可以设置采集阈值,只有达到一定阈值才会被记录。和instat的区别就是这个有持续的状态,例如锁的等待超过一定时间。
  3. sample event。采样事件,我们常见的堆栈采样就是这种类型。

第二个维度是基于jdk工程的分类。了解这个是为了更好的理解,我们能更好的理解我们的能力范围。

  1. native event。这个是hotspot里的埋点。声明见github.com/openjdk/jdk...
  2. jdk内部event。这个埋点在jdk.internal里。主要是jdk工程自己的event,相当于jdk自身提供给外部观测的事件。声明见github.com/openjdk/jdk...
  3. java event。这个埋点在jdk.jfr, java event相当于我们的core lib中的实现的事件。声明见github.com/openjdk/jdk...

2和3的区别仅限于工程的层次。2边自定义了不同的框架方便编码。在实现的角度看是一样的。

基于上面的介绍。我们如果自定义的话,基本都是在java event中增加自己的java事件。

自定义event

自定义event我们就参考jdk自己的代码来学习。不过我们自己得结合2和3的方式来做,我们自己写代码接近于2,但是声明方式接近于3。 我们先基于2的实现来看。

java 复制代码
public final class ProcessStartEvent extends Event {

public long pid;

public String directory;

public String command;

}

首先是构造一个event,上面是启动子进程的一个事件,不过这里是event是内部实现,我们自定义的时候导入的包是不一样的。

在使用的时候在代码中直接埋点。

java 复制代码
Process process = ProcessImpl.start(cmdarray,
environment,dir,redirects,redirectErrorStream);

ProcessStartEvent event = new ProcessStartEvent();

if (event.isEnabled()) {

event.directory = dir;

event.command = String.join(" ", cmdarray);

event.pid = process.pid();

event.commit();

}

和我们常见的埋点方式没有什么不同,在执行完之后,new一个对应的event,然后把需要采集的内容设置进去。然后调用commit就可以了。这里同时解释了上面instant event记录的力度,完全是采集代码写的。只要是能获取到的都可以。

我们也介绍3的声明方式以及为什么这么不如2直接。

java 复制代码
@Name(Type.EVENT_NAME_PREFIX + "FileForce")

@Label("File Force")

@Category("Java Application")

@Description("Force updates to be written to file")

public final class FileForceEvent extends AbstractJDKEvent {

  


// The order of these fields must be the same as the parameters in

// commit(..., String, boolean)

  


@Label("Path")

@Description("Full path of the file")

public String path;

  


@Label("Update Metadata")

@Description("Whether the file metadata is updated")

public boolean metaData;

  


public static void commit(long start, long duration, String path, boolean metaData) {

// Generated

}

}

这是file force的事件,可以看到他有一堆的lable。这个地方是方便我们后续解析jfr event使用的。还有对应的说明。可维护性更好。我们自定义的时候,希望是加上各种lable。

这里也解释一下他的写法为什么是这样的。 这里是为了让事件和实现解耦。也方便做裁剪,openjdk后续不提供jre了,需要自己裁剪jdk生产自己想要的jre。这里就有可能有人把jfr给裁剪掉。他虽然声明了这么一个event。在使用的时候,依靠动态字节码生成技术。注入对应的方法。所以我们看到commit方法是空的实现,这里也是动态加入的。这里简单介绍一下他们的对应关系。

java 复制代码
@JIInstrumentationTarget("sun.nio.ch.FileChannelImpl")

final class FileChannelImplInstrumentor {


private FileChannelImplInstrumentor() {

}

private String path;


@SuppressWarnings("deprecation")

@JIInstrumentationMethod

public void force(boolean metaData) throws IOException {

EventConfiguration eventConfiguration = EventConfigurations.FILE_FORCE;

if (!eventConfiguration.isEnabled()) {

force(metaData);

return;

}

long start = 0;

try {

start = EventConfiguration.timestamp();

force(metaData);

} finally {

long duration = EventConfiguration.timestamp() - start;

if (eventConfiguration.shouldCommit(duration)) {

FileForceEvent.commit(start, duration, path, metaData);

}

}

}
}

这里是使用event的地方,我们可以看到他的标签是注入sun.nio.ch.FileChannelImpl。然后JIInstrumentationMethod表示注入的方法。等于是对FileChannelImpl的force做了一层代理,在正常执行完之后,就会调用原来的force(metaData);这里就看到了其实2和3的实现是一样的。只不过3做了一层控制,把采集代码和使用代码做了解耦。最终靠字节码增强技术拼接出了方法调用。

我们自己的工程就不用这么麻烦了,都是自己的代码,埋点直接加入即可。

小结

当我们没有特别复杂的观测系统的时候,可以直接把事件记录到jfr中。这么记录的模式非常接近debug日志,对比日志的优势是他可以低overhead的获取到堆栈,例如想知道谁调用了这个方法,jfr是比日志更好的选择。

相关推荐
yuren_xia2 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
JohnYan5 小时前
Bun技术评估 - 04 HTTP Client
javascript·后端·bun
shangjg35 小时前
Kafka 的 ISR 机制深度解析:保障数据可靠性的核心防线
java·后端·kafka
青莳吖6 小时前
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
前端·spring boot·后端
我的golang之路果然有问题7 小时前
ElasticSearch+Gin+Gorm简单示例
大数据·开发语言·后端·elasticsearch·搜索引擎·golang·gin
mldong8 小时前
我的全栈工程师之路:全栈学习路线分享
前端·后端
噼里啪啦啦.9 小时前
SpringBoot统一功能处理
java·spring boot·后端
考虑考虑9 小时前
JPA自定义sql参数为空和postgresql遇到问题
spring boot·后端·spring
橘子青衫10 小时前
Java多线程编程:深入探索线程同步与互斥的实战策略
java·后端·性能优化
shengjk110 小时前
一文搞懂 python __init__.py 文件
后端