JaCoCo探针策略原理及案例总结
发布日期:2021-06-30 12:23:41 浏览次数:2 分类:技术文章

本文共 2409 字,大约阅读时间需要 8 分钟。

1 探针策略

  • 在一串字节码指令中插入这些探针,只要该探针被执行了,说明其之前的指令都被执行了
  • 注意方法结束了是在 return 指令前放置探针哦
  • 跳转语句的记录
  • 条件语句

2 探针特点

探测的唯一目的是记录它至少执行过一次。探测器不记录它被调用的次数或收集任何时间信息。后者超出了代码覆盖率分析的范围,更多的是在性能分析工具的目标中

  • 最小的运行时间开销
  • 对应用程序代码无副作用
  • 线程安全
  • 记录字节码的执行
  • 标识不同类型探针
    使用的 boolean 数组记录对应的指令是否被执行

3 为什么最小的性能开销?

javap -c Fun

...       0: getstatic     #2                  // Field $assertionsDisabled:Z       3: ifne          18       6: iload_1       7: ifne          18      10: new           #3                  // class java/lang/AssertionError      13: dup      14: invokespecial #4                  // Method java/lang/AssertionError."
":()V 17: athrow 18: return...
  • 其实翻译过来代码就是这样子
  • 通过将这种原理用于 jacoco,降低了性能开销

4 如何实现代码注入

JaCoCo通过ASM在字节码中插入Probe指针(探测指针),每个探测指针都是一个BOOL变量(true表示执行、false表示没有执行),程序运行时通过改变指针的结果来检测代码的执行情况(不会改变原代码的行为).

增量注入

JaCoCo默认全量注入.

源码中注入的逻辑主要在ClassProbesAdapter

ASM在遍历字节码时,每次访问一个方法定义,都会回调这个类的visitMethod方法 ,在visitMethod方法中再调用ClassProbeVisitorvisitMethod方法,并最终调用MethodInstrumenter完成注入。部分代码片段如下:

@Overridepublic final MethodVisitor visitMethod(final int access, final String name,		final String desc, final String signature,		final String[] exceptions) {
final MethodProbesVisitor methodProbes; final MethodProbesVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv == null) {
// 无论如何,我们都需要访问该方法,否则探针的ID无法重现 methodProbes = EMPTY_METHOD_PROBES_VISITOR; } else {
methodProbes = mv; } return new MethodSanitizer(null, access, name, desc, signature, exceptions) {
@Override public void visitEnd() {
super.visitEnd(); LabelFlowAnalyzer.markLabels(this); final MethodProbesAdapter probesAdapter = new MethodProbesAdapter( methodProbes, ClassProbesAdapter.this); if (trackFrames) {
final AnalyzerAdapter analyzer = new AnalyzerAdapter( ClassProbesAdapter.this.name, access, name, desc, probesAdapter); probesAdapter.setAnalyzer(analyzer); methodProbes.accept(this, analyzer); } else {
methodProbes.accept(this, probesAdapter); } } };}

自动获取运行时数据

代码中通过反射执行下面的函数来获取运行时数据,并保存到当前执行代码的设备中:

org.jacoco.agent.rt.RT.getAgent().getExecutionData(false)

生成报告时需要用到运行时数据,为了生成的覆盖率报告更准确、开发同学用起来更方便,分别在如下时机把运行时数据保存到当前设备中:

  • 每个页面执行onDestory时
  • 程序发生崩溃时
  • 收到特定广播(一个自定义的广播,在执行生成覆盖率报告的task前发送)时

并在生成覆盖率报告之前把设备中的运行时数据同步到本地开发环境中。

上面可以看到,因为获取时机比较多,可能会得到多份运行时数据,对于这些数据,可以通过JaCoCo的mergeTask把ClassId相同的运行时数据进行merge。如下图所示,JaCoCo会对ClassId相同的运行时数据进行merge,并对相同位置的probe指针取或:

转载地址:https://javaedge.blog.csdn.net/article/details/105044476 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:阿里三面:说说线程封闭与ThreadLocal的关系
下一篇:计算机中的dump到底是什么意思?

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月19日 13时40分10秒