在前面看Spring源码的过程中,有下面的代码:
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create");
// BeanDefinition读取器,可以注册BeanDefinition,也可以读取BeanDefinition
// 本质上就是BeanDefinitionRegistry,多了ConditionEvaluator,以及注册了一些默认的BeanPostProcessor
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
// BeanDefinition扫描器,负责扫描
this.scanner = new ClassPathBeanDefinitionScanner(this);
}后面的代码中也会看到getApplicationStartup()的start和end方法调用,本文我们来细节了解下这个是干什么的。
本节的代码是直接从上节拷贝的SpringBoot工程
源码初探
顺着getApplicationStartup()方法点进去,可以知道Spring默认提供了两个实现类,其中DefaultApplicationStartup是比较简单的,它的start方法就是返回对象本身,end方法是空方法,所以默认相当于getApplicationStartup().start()与getApplicationStartup().end()这两份行代码都没有做任何的事情。

而另一个实现类,就是我们今天要说的JFR。
什么是JFR?
JFR全称为JDK Flight Recorder,是JVM中的一个事件记录器,相当于飞机中的数据飞行记录器(黑匣子),用于记录应用程序在工作过程中发生的各种事件的情况,默认内置了很多事件,比如GC事件:

上图表示做了4次Young GC,1次Old GC,共5次,每次GC的开始时间、总耗时都有记录。
GC Identifier表示本次GC的ID,可以通过它查看GC前后的变化:

默认还有很多其他事件(比如:操作系统层面内存的、CPU的),本文就不一一分析的,大家可自行体验和研究。

开启这个会有一定的性能损耗
如何开启JFR机制?
默认没有开启JFR,想要使用JFR,只需要添加-XX:StartFlightRecording这个JVM参数即可,添加了参数后,在启动应用程序时就会出现以下提示:

表示可以在程序运行过程中使用jcmd 9600 JFR.dump name=1 filename=FILEPATH命令来生成jfr文件,从而可以查看在执行命令之前应用程序内部发生的各种事件的情况。
其中,9600表示当前应用程序的进程id,FILEPATH需要改成jfr文件存放的路径和文件名,应用程序运行过程中,可以多次执行以上命令,生成的jfr文件如果重名会直接覆盖。比如用如下命令将jfr文件导出存放在项目根目录的test.jfr文件中:
jcmd 9600 JFR.dump name=1 filename=test.jfr可以利用JMC工具或在IDEA中直接双击jfr文件就能看到具体的事件信息了。
如何自定义JFR事件?
定义如下:
/**
* 自定义JFR事件
**/
@Category("类别A")
@Label("标签A")
public class MyJfrEvent extends Event {
@Label("事件名称")
private String eventName; // 自定义属性,可以通过构造器或者setter赋值
public MyJfrEvent(String eventName) {
this.eventName = eventName;
}
}使用如下:
@Service
public class UserService {
public String test() {
MyJfrEvent event = new MyJfrEvent("事件A");
event.begin();
// 业务逻辑
event.commit();
return "success";
}
}多次调用test方法,重新导出jfr文件,最终效果如下:

我们可以自定义事件的主类别、子类别、事件的属性等,默认情况下会自动记录事件的时间和线程情况。
Spring中的JFR事件
在Spring源码工程的测试模块中,我们也添加-XX:StartFlightRecording这个JVM参数再运行测试,代码如下:
public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.test();
System.in.read(); // 保持阻塞,防止进程停止
}
}此时导出jfr文件,打开查看:

看不到Spring的事件分类,这是因为默认用的是ApplicationStartup.DEFAULT相当于没有记录事件。只要我们改造下代码的写法即可:
public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setApplicationStartup(new FlightRecorderApplicationStartup());
applicationContext.register(MyConfig.class);
applicationContext.refresh();
UserService userService = applicationContext.getBean(UserService.class);
userService.test();
System.in.read(); // 保持阻塞,防止进程停止
}
}可以看到Spring中也利用了JFR事件机制定义了很多事件,比如

其中spring.context.refresh表示刷新容器的总耗时,spring.context.instantiate表示创建某个Bean的耗时,tags中记录了对应Bean的名字,这样,我们也可以通过JFR机制查看Spring在启动过程中各个步骤的耗时情况。