Java动态追踪技术--BTrace

Java动态追踪技术

  • 需求翻译官的日常工作不是在的改bug,发布代码,就是在加日志查找bug的路上。查BUG的过程是痛苦的,我们总是在不停的看代码,修改代码,添加日志,从而帮助我们发现问题,这种形式是比较繁琐的,不断的在发布项目,开发效率也不高,而且线上的问题排查不可能让你经常添加调试代码,能不能找到一种和JSP一样快捷的方式呢。
JSP模式
  • 对应大多数程序员来说,早期的时候都接触过JSP(java server pages)。虽然之后在前后端分离的主流思想下主键淘汰不用了,但是JSP还是有一些比较值得我们去思考的点,例如我们在使用JSP写页面效果的时候,修改代码只需要刷新页面,而不需重启服务,就可以看到页面展示效果。并不需要重启JVM。

  • 按照我们的常识,java程序一般都需要启动时候加载类文件,如果像JSP这样修改完代码,不用重启就生效,那么我们就不用这么痛苦的查bug,直接加代码又不用发,加日志看下就解决了。其实是JSP的运行机制和java不一样,当我们打开浏览器,请求一个JSP文件有如下流程:
    在这里插入图片描述

  • JSP文件修改后,之所以能及时生效,是因为tomcate会检查请求的JSP文件是否被更改过。如果发送过更改,呢么就会将JSP文件重新解析,翻译成一个新的java,从而产生新的servlet类,加载到jvm中。

  • 但是此处有一个问题,根据java类加载机制,同一个classLoader中,类是不允许重复的。为绕开这个限制,Tomcate每次会创建一个新的ClassLoader实例,来加载新编译的servlet类。之后的请求都会有这个新的Servlet来处理,这样就实现了新旧JSP的切换。

  • HTTP服务是无状态的,所以JSP的场景基本上是一次性消费,请求后给当前解析后的jsp内容,这种通过ClassLoader来替换class的方法在JSP上可行,因为有一个翻译过程,但是在Spring应用中,对象大多是单例,存在内存中,就算创建新ClassLoader也要全部在处理一次,不太现实,所有这种方式在Spring项目中不可行。

  • BTrace 插桩

java 对象行为
  • java对象使用两种东西来描述事物: 方法,属性
    • java对象的属性跟着对象走,每个对象存储一份
    • java对象的方法,函数存储在方法区
      在这里插入图片描述
  • 如上图:
    • 方法区中数据是类加载时候从class文件中取出
    • class文件是从java或者其他符号jvm规范的源码中编译来的
    • 源码我们我们可以自己控制
  • 通过上面几个步骤我们能否找到办法去修改需要加载的类,从而达到我们的目的,并且需要满足几个条件:
    • 需要修改字节码中目标方法所在区域,然后重新加载这个类
    • 只修改调用方法,不修改对象的属性,也不印象已经存在的对象状态
    • 不违背jvm类加载原理,也就是这个类还是这个类,还是同一个ClassLoader
  • 还真有:java.lang.instrument.Instrumentation
Instrumentation
  • 我们来查一下Java API中对这个类的描述信息:
该类提供了用于设计Java编程语言代码所需的服务。 仪器是向方法添加字节码,用于收集工具要使用的数据。 由于这些更改纯粹是加法的,因此这些工具不会修改应用程序的状态或行为。 这种良性工具的示例包括监视代理,剖析器,覆盖分析器和事件记录器。
  • 文档描述中有两个我们能用得到的方法:redefineClasses和retransformClasses。一个重新定义class,一个修改class。这两个差不多功能,
//   使用提供的类文件重新定义提供的一组类。
void redefineClasses(ClassDefinition... definitions)throws ClassNotFoundException,UnmodifiableClassException
//重新转换提供的一组类。
void retransformClasses(<?>... classes)throws UnmodifiableClassException
  • 两个方法都是替换已经存在的class文件,redefineClasses是自己提供字节码文件替换已经存在的class,retransformClasses是在已经存在的字节码文件上修改后在替换。
  • 既然JDK提供了这种API,那么我们可以在编译得到class文件后,在通过redefineClass替换,就能加日志,修改class文件,从而达到上文中不重启修改的目的。
直接操作字节码
  • 我们通过JDK的api来修改了本要加载的一个Class字节码文件,字节码文件也是程序语言,只不过人类不好理解,可读性远没有java代码高。
  • 一般人都不会去直接修改字节码文件,但是,有一部分杰出的程序员,创造出来可以直接编辑字节码的框架,提供接口可以让我们方便的去操作字节码文件,进行注入修改类的方法,动态创造一个新的类等等操作。其中最著名的就是ASM,现在我们接触的cglib,Spring等框架中对字节码的操作就是基于ASM上的,
实现方式
  • 截止目前都是针对开始遇到的问题的理论层面的可行性研究,那么我们怎么实现,而且修改线上的字节码文件简直就是在作死,而且实施起来会有意想不到的困难:

    • 寻找工程中的这个字节码
    • 修改这个字节码,然后reTransform这个字节码
    • 我们无法预知某个程序出错需要去修改字节码,也不可能每个工程都开发一段专门的程序去修改字节码,并且重新加载改代码,这样成本太高
    • 即使我们能解决上面的问题,我们也不一定会用ASM,需要更通用的办法
    • JVM不在本地,在远程,我们也是需要解决的问题
    • 修改线上JVM中字节码文件简直在作死,这存在巨大的安全性问题
  • 幸运的是,已经有人吧这么都搞定了,因为有一个开源工具BTrace的存在,描述非常精炼:

A safe, dynamic tracing tool for the Java platform
  • BTrace 是基于Java预约的一个安全,可提供动态追踪服务的工具。BTrace基于ASM,Java Attach API, Instruments开发,为用户提供很多注解,依靠这些注解,可以编写BTrace脚本(简单的Java脚本)达到我们的目的,而不必对ASM有深刻理解。

  • 我们看下Attach 的api

  • 看BTrace官网的一个简单例子:拦截所有java.io包中所有类以read开头的方法,打印类名,方法名,参数名。当程序IO负载比较高的时候,可以从输出的信息看到是哪些类引起的,如此的方便:

package com.sun.btrace.samples;import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;
import static com.sun.btrace.BTraceUtils.*;/*** This sample demonstrates regular expression* probe matching and getting input arguments* as an array - so that any overload variant* can be traced in "one place". This example* traces any "readXX" method on any class in* java.io package. Probed class, method and arg* array is printed in the action.*/
@BTrace public class ArgArray {@OnMethod(clazz="/java\\.io\\..*/",method="/read.*/")public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {println(pcn);println(pmn);printArray(args);}
}
先理解在应用
  • 既然BTrace能解决以上的问题,那么他是怎么做到的,他的架构是怎么样的,我们可以从管网的信息中找到答案:

  • BTrace主要有一下几个模块

    • BTrace脚本:利用BTrace定义的注解,我们可以很方便的更具需要进行脚本开发
    • Compiler(编译):将BTrace脚本编译成BTrace class文件
    • Client:将Class文件发送到Agent
    • Agent:基于Java的Attach Api,Agent可以动态附着一个运行的JVM上,然后开启一个BTrace Server,接受Client发过来的BTrace脚本。解析脚本然后根据脚本中的规则找到要修改的类;然后调用Java Instruments的reTransform接口完成对象行为的修改并使之生效。

在这里插入图片描述

在这里插入图片描述

安全性
  • 如上流程最终借助Java instruments 和Java Attach api实现class替换,出于安全考虑,instruments在使用上会有诸多的限制:
    • 不允许创建对象
    • 不允许创建数组
    • 不允许抛异常
    • 不允许catch异常
    • 不允许随意调用其他对象或者类的方法,只允许调用com.sun.btrace.BTraceUtils中提供的静态方法(一些数据处理和信息输出工具)
    • 不允许改变类的属性
    • 不允许有成员变量和方法,只允许存在static public void方法
    • 不允许有内部类、嵌套类
    • 不允许有同步方法和同步块
    • 不允许有循环
    • 不允许随意继承其他类(当然,java.lang.Object除外)
    • 不允许实现接口
    • 不允许使用assert
    • 不允许使用Class对象
  • 如此多的限制,其实可以理解。BTrace要做的是,虽然修改了字节码,但是除了输出需要的信息外,对整个程序的正常运行并没有影响。
应用
  • 应用详见本人git项目
Arthas
  • BTrace脚本使用上也有一定学习成本,如果吧常用功能封装好,提供简单命令,那就编程了Archas
最后
  • 总结我们之前的知识点,Java的Instruments给运行时的动态追踪留下了一个初始APi,Attach API则给运行时动态追踪提供了“出入口”,ASM则大大方便了“人类”操作Java字节码的操作。
  • 所以基于以上JDK api,前辈们创造出来诸如JProfiler, JVisualvm,BTrace,Archas这样的工具。
  • 以上大大提高了软件开发人民定位问题的效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310486.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#黔驴技巧之实现统计结果排名

本节是实现统计排名的一点技巧&#xff0c;可能有一部分童鞋在实现排名统计绕了一大圈&#xff0c;最后还不如两行代码就搞定&#xff0c;代码清晰而可读性强&#xff0c;接下来我们来一起来看看。我们知道在SQL Server中可以利用Row_Number、Rank等函数进行排名&#xff0c;在…

数据结构与算法--二叉树实现原理

二叉树 二叉树&#xff08;binary tree&#xff09;是一棵树&#xff0c;其中每个节点都不能有多于两个的子节点二叉树的一个性质是一颗平均二叉树的深度要比节点个数N小得多&#xff08;重点&#xff09;&#xff0c;对二叉树的分析得出其平均深度为O(N\sqrt NN​)&#xff0…

[C++STL]C++实现stack容器适配器

代码如下: #include <iostream> #include <deque> using namespace std;template<typename T,typename Con deque<T>> class Stack { public:Stack() {}void Push(const T &x) {_c.push_back(x);}void Pop(){_c.pop_back();}T & Top(){retur…

Istio Pilot架构解析

本文节选自 ServiceMesher 社区联合编写的《Istio Handbook——Istio 服务网格进阶实战》。本书地址&#xff1a;https://github.com/servicemesher/istio-handbook/在应用从单体架构向微服务架构演进的过程中&#xff0c;微服务之间的服务发现、负载均衡、熔断、限流等服务治理…

数据结构与算法--二叉查找树实现原理

二叉查找树 二叉树的一个重要应用就是他在查询中的使用&#xff0c;假设书中每个节点存储一项数据。在我们的案例中&#xff0c;任意复杂的项在java中都容易处理&#xff0c;但为了简单还是假设都是整数。还假设他们都是不重复的整数&#xff0c;使二叉树称为二叉查找树的性质…

[C++STL]C++实现queue容器适配器

代码如下: #include <iostream> #include <deque>using namespace std;template<typename T,typename Con deque<T>> class Queue { public:Queue(){}void Push(const T & x){_c.push_back(x);}void Pop(){_c.pop_front();}T &Back(){return…

当模板方法遇到了委托函数,你的代码又可以精简了

现如今当你翻看一些开源项目源码的时候&#xff0c;你会发现现在到处充斥着委托函数&#xff0c;如Func,Action,Predicate&#xff0c;确实现在的C#在函数式编程 的路上越来越成为主流&#xff0c;越来越显示威力&#xff0c;曾经的一些经典设计模式写法&#xff0c;在函数式下…

数据结构与算法--面试必问AVL树原理及实现

数据结构与算法–AVL树原理及实现 AVL&#xff08;Adelson-Velskii 和landis&#xff09;树是带有平衡条件的二叉查找树&#xff0c;这个平衡条件必须容易实现&#xff0c;并且保证树的深度必须是O(logN)。因此我们让一棵AVL树中每个节点的左子树和右子树的高度最多相差1&…

MySQL8.0新特性

在这之前Mysql的版本是5.7&#xff0c;也是目前使用最广泛的一个版本。现在新版本跳过了6和7直接来到了8&#xff0c;那么V6和V7版本去哪里了呢&#xff1f;比较靠谱的说法是v6用作了内部的其他用途而v7的话是因为mysql有个产品叫做clusterdb他有7这个版本&#xff0c;所以这个…

数据结构与算法--B树原理及实现

B树 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中。可说总要那么海量的数据需要通过个中数据结构去存储&#xff0c;我们不可能有这么多内存区存放这些数据。那么意味着我们需要将他们放磁盘。所以这个时候范问时间复杂度O决定了他是否能适合存储磁盘…

[C++STL]C++实现priority_queue容器适配器

代码如下: #pragma once #include <iostream> #include <vector> using namespace std;template<typename T> struct Less {bool operator()(const T &a, const T &b){return a < b;} };template<typename T> struct Greater {bool operat…

为什么要用内插字符串代替string.format

知道为什么要用内插字符串&#xff0c;只有踩过坑的人才能明白&#xff0c;如果你曾今使用string.format超5个以上占位符&#xff0c;那其中的痛苦我想你肯定是能够共鸣的。一&#xff1a;痛苦经历先上一段曾今写过的一段代码&#xff0c;大家来体会一下&#xff1a;LogHelper.…

SpringCloud Alibaba 框架下公司架构图

上一篇&#xff1a;Docker容器实战思维 下一篇&#xff1a;分布式事务理论模型

C++ 实现堆

代码如下(小根堆): #include <iostream> #include <assert.h> using namespace std;//小根堆,堆排序从大到小排 class Heap { public:Heap():data(nullptr), size(0), capacity(0) {}~Heap(){delete[] data;data nullptr;size 0;capacity 0;}/*void Swap(int *…

iPhone上运行Linux也要来了

FOSSBRTES 报道称&#xff0c;用户很快将可以通过双启动功能&#xff0c;像在 Android 设备上那样在 iPhone 上运行 Linux。目前&#xff0c;iOS 越狱极客、开发人员 Raffaele 以及 mcg29 已在其 Github 页面上发布了详细说明&#xff0c;详细介绍了如何双启动 64 位 iOS 设备。…

数据结构与算法--图论,最短路算法,拓扑排序算法

图论若干定义 图&#xff08;graph&#xff09;G&#xff08;V,E&#xff09;由定点vertex的集合V&#xff0c; 和边edge的集合E组成。每一条边都是一个点对点&#xff08;v&#xff0c;w&#xff09;&#xff0c;其中 v,w 属于V集合的子集 如果点对点 是有序的&#xff0c;那…

谁说.NET不适合搞大数据、机器学习和人工智能

SciSharp StackSciSharp STACK: https://scisharp.github.io/SciSharp/基于.NET的开源生态系统&#xff0c;用于数据科学、机器学习和AI。SciSharp将所有主要的ML/AI框架从Python引入.NET.特点为.NET开发者.NET开发者使用他们所了解和喜爱的工具可以最高效的工作。我们的使命是…

C++ 泛型编程 实现红黑树RBTree

代码如下: #include <iostream> #include <ctime> using namespace std;enum COLOR {BLACK,RED };template<typename T> struct RBTreeNode {RBTreeNode<T> * _parent;RBTreeNode<T> * _left;RBTreeNode<T> * _right;T _val;COLOR _color…

数据结构与算法--图论-深度优先搜索及其应用

深度优先搜索 深度优先搜索&#xff08;depth-first search&#xff09; 是对先序遍历&#xff08;preorder traversal&#xff09;的推广&#xff0c;我们从某个顶点v开始处理v&#xff0c;然后递归的遍历所有与v邻接顶点。如果这个过程是对一棵树进行&#xff0c;那么&#…

.NET Core技术研究-主机

前一段时间&#xff0c;和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程但是没有深入说明主机的设计。今天整理了一下主机的一些知识&#xff0c;结合先前的博文&#xff0c;完整地介绍一下.NET Core的主机的设计和构建启动过程。一、什么是主机主机是一个封装了应用资…