java.lang.instrument.Instrumentation
java.lang.instrument.Instrumentation
看完文档之后,我们发现这么两个接口:redefineClasses和retransformClasses。一个是重新定义class,一个是修改class。这两个大同小异,看redefineClasses的说明:
This method is used to replace the definition of a class without reference to the existing class file bytes, as one might do when recompiling from source for fix-and-continue debugging. Where the existing class file bytes are to be transformed (for example in bytecode instrumentation) retransformClasses should be used.
都是替换已经存在的class文件,redefineClasses是自己提供字节码文件替换掉已存在的class文件,retransformClasses是在已存在的字节码文件上修改后再替换之。
当然,运行时直接替换类很不安全。比如新的class文件引用了一个不存在的类,或者把某个类的一个field给删除了等等,这些情况都会引发异常。所以如文档中所言,instrument存在诸多的限制:
The redefinition may change method bodies, the constant pool and attributes. The redefinition must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance. These restrictions maybe be lifted in future versions. The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception.
我们能做的基本上也就是简单修改方法内的一些行为,这对于我们开头的问题,打印一段日志来说,已经足够了。当然,我们除了通过retransform来打印日志,还能做很多其他非常有用的事情,这个下文会进行介绍。
那怎么得到我们需要的class文件呢?一个最简单的方法,是把修改后的Java文件重新编译一遍得到class文件,然后调用redefineClasses替换。但是对于没有(或者拿不到,或者不方便修改)源码的文件我们应该怎么办呢?其实对于JVM来说,不管是Java也好,Scala也好,任何一种符合JVM规范的语言的源代码,都可以编译成class文件。JVM的操作对象是class文件,而不是源码。所以,从这种意义上来讲,我们可以说“JVM跟语言无关”。既然如此,不管有没有源码,其实我们只需要修改class文件就行了。
直接操作字节码
Java是软件开发人员能读懂的语言,class字节码是JVM能读懂的语言,class字节码最终会被JVM解释成机器能读懂的语言。无论哪种语言,都是人创造的。所以,理论上(实际上也确实如此)人能读懂上述任何一种语言,既然能读懂,自然能修改。只要我们愿意,我们完全可以跳过Java编译器,直接写字节码文件,只不过这并不符合时代的发展罢了,毕竟高级语言设计之始就是为我们人类所服务,其开发效率也比机器语言高很多。
对于人类来说,字节码文件的可读性远远没有Java代码高。尽管如此,还是有一些杰出的程序员们创造出了可以用来直接编辑字节码的框架,提供接口可以让我们方便地操作字节码文件,进行注入修改类的方法,动态创造一个新的类等等操作。其中最著名的框架应该就是ASM了,cglib、Spring等框架中对于字节码的操作就建立在ASM之上。
我们都知道,Spring的AOP是基于动态代理实现的,Spring会在运行时动态创建代理类,代理类中引用被代理类,在被代理的方法执行前后进行一些神秘的操作。那么,Spring是怎么在运行时创建代理类的呢?动态代理的美妙之处,就在于我们不必手动为每个需要被代理的类写代理类代码,Spring在运行时会根据需要动态地创造出一个类。这里创造的过程并非通过字符串写Java文件,然后编译成class文件,然后加载。Spring会直接“创造”一个class文件,然后加载,创造class文件的工具,就是ASM了。
到这里,我们知道了用ASM框架直接操作class文件,在类中加一段打印日志的代码,然后retransform就可以了。
BTrace
截止到目前,我们都是停留在理论描述的层面。那么如何进行实现呢?先来看几个问题:
在我们的工程中,谁来做这个寻找字节码,修改字节码,然后retransform的动作呢?我们并非先知,不可能知道未来有没有可能遇到文章开头的这种问题。考虑到性价比,我们也不可能在每个工程中都开发一段专门做这些修改字节码、重新加载字节码的代码。
如果JVM不在本地,在远程呢?
如果连ASM都不会用呢?能不能更通用一些,更“傻瓜”一些。
幸运的是,因为有BTrace的存在,我们不必自己写一套这样的工具了。什么是BTrace呢?BTrace已经开源,项目描述极其简短:
A safe, dynamic tracing tool for the Java platform.
BTrace是基于Java语言的一个安全的、可提供动态追踪服务的工具。BTrace基于ASM、Java Attach API、Instrument开发,为用户提供了很多注解。依靠这些注解,我们可以编写BTrace脚本(简单的Java代码)达到我们想要的效果,而不必深陷于ASM对字节码的操作中不可自拔。
看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);
}
}
再来看另一个例子:每隔2秒打印截止到当前创建过的线程数。
package com.sun.btrace.samples;
import com.sun.btrace.annotations.;
import static com.sun.btrace.BTraceUtils.;
import com.sun.btrace.annotations.Export;
/**
This sample creates a jvmstat counter and
increments it everytime Thread.start() is
called. This thread count may be accessed
from outside the process. The @Export annotated
fields are mapped to jvmstat counters. The counter
name is "btrace." + + "." +
*/
@BTrace public class ThreadCounter {// create a jvmstat counter using @Export
@Export private static long count;@OnMethod(
clazz="java.lang.Thread",
method="start"
)
public static void onnewThread(@Self Thread t) {
// updating counter is easy. Just assign to
// the static field!
count++;
}@OnTimer(2000)
public static void ontimer() {
// we can access counter as "count" as well
// as from jvmstat counter directly.
println(count);
// or equivalently ...
println(Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count"));
}
}
看了上面的用法是不是有所启发?忍不住冒出来许多想法。比如查看HashMap什么时候会触发rehash,以及此时容器中有多少元素等等。
有了BTrace,文章开头的问题可以得到完美的解决。至于BTrace具体有哪些功能,脚本怎么写,这些Git上BTrace工程中有大量的说明和举例,网上介绍BTrace用法的文章更是恒河沙数,这里就不再赘述了。
我们明白了原理,又有好用的工具支持,剩下的就是发挥我们的创造力了,只需在合适的场景下合理地进行使用即可。
既然BTrace能解决上面我们提到的所有问题,那么BTrace的架构是怎样的呢?
BTrace主要有下面几个模块:
BTrace脚本:利用BTrace定义的注解,我们可以很方便地根据需要进行脚本的开发。
Compiler:将BTrace脚本编译成BTrace class文件。
Client:将class文件发送到Agent。
Agent:基于Java的Attach API,Agent可以动态附着到一个运行的JVM上,然后开启一个BTrace Server,接收client发过来的BTrace脚本;解析脚本,然后根据脚本中的规则找到要修改的类;修改字节码后,调用Java Instrument的retransform接口,完成对对象行为的修改并使之生效。
整个BTrace的架构大致如下:

小小登录,大大讲究!你的登录功能都做到位了吗?
BTrace最终借Instrument实现class的替换。如上文所说,出于安全考虑,Instrument在使用上存在诸多的限制,BTrace也不例外。BTrace对JVM来说是“只读的”,因此BTrace脚本的限制如下:
不允许创建对象
不允许创建数组
不允许抛异常
不允许catch异常
不允许随意调用其他对象或者类的方法,只允许调用com.sun.btrace.BTraceUtils中提供的静态方法(一些数据处理和信息输出工具)
不允许改变类的属性
不允许有成员变量和方法,只允许存在static public void 方法
不允许有内部类、嵌套类
不允许有同步方法和同步块
不允许有循环
不允许随意继承其他类(当然,java.lang.Object除外)
不允许实现接口
不允许使用assert
不允许使用Class对象
如此多的限制,其实可以理解。BTrace要做的是,虽然修改了字节码,但是除了输出需要的信息外,对整个程序的正常运行并没有影响。
Arthas
BTrace脚本在使用上有一定的学习成本,如果能把一些常用的功能封装起来,对外直接提供简单的命令即可操作的话,那就再好不过了。阿里的工程师们早已想到这一点,就在去年,阿里巴巴开源了自己的Java诊断工具——Arthas
Arthas提供简单的命令行操作,功能强大。究其背后的技术原理,和本文中提到的大致无二。
本文旨在说明Java动态追踪技术的来龙去脉,掌握技术背后的原理之后,只要愿意,各位读者也可以开发出自己的“冰封王座”出来。
https://mp.weixin.qq.com/s/5DWFAD-_5hSnSroij22iIg
https://mp.weixin.qq.com/s/437sTI7oihi9EEF6MNZjUg
java.lang.instrument.Instrumentation的更多相关文章
- java.lang.instrument使用
Java在1.5引入java.lang.instrument,你可以由此实现一个Javaagent,通过此agent来修改类的字节码即改变一个类. 程序启动之时启动代理(pre-main) 通过jav ...
- J2SE 1.6 特性:java.lang.instrument
1. import java.lang.instrument.Instrumentation; public class ObjectSizeFetcher { private static Inst ...
- java.lang.instrument: 一个Java对象占用多少字节?
一.对象头包括两部分信息:Mark Word(标记字段)和 Klass Pointer(类型指针) 1. Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode).GC分代年 ...
- java.lang.instrument 中的premain 实现类的个性化加载(附源代码)
背景 想调用ASM API (用于字节码处理的开源API)对字节码进行处理,目标是实现对java程序运行时各种对象的动态跟踪,并进一步分析各个对象之间的关系(研究前提是目前的UML锁阐释的whole- ...
- java.lang包
作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/ 1.特性——不用import 2.String String x = "abc"; < ...
- jdk研究——java.lang
jdk研究 volatile 是什么意思? 如何看jdk源码? 如何调试源码!---------仔细解读关键类,关键代码,常用的api的解释! 自己有疑问的不懂地方-------- 不懂的太多怎么办. ...
- "java.lang.IllegalStateException: No instrumentation registered! Must run under a registering instrumentation."问题解决
问题描述 运行uiautomator,报错"java.lang.IllegalStateException: No instrumentation registered! Must run ...
- 应用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap space)
http://www.educity.cn/wenda/351088.html 使用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap s ...
- 1、Android Studio集成极光推送(Jpush) 报错 java.lang.UnsatisfiedLinkError: cn.jpush.android.service.PushProtoco
Android studio 集成极光推送(Jpush) (华为手机)报错, E/JPush: [JPushGlobal] Get sdk version fail![获取sdk版本失败!] W/Sy ...
随机推荐
- 想成为Java高手的25个学习目标
本文将告诉你学习Java需要达到的25个目标,希望能够对你的学习及找工作有所帮助.对比一下自己,你已经掌握了这25条中的多少条了呢? 1.你需要精通面向对象分析与设计(OOA/OOD).涉及模式(GO ...
- PE文件头格式解析
前言: 昨天写了一题de1ctf的题,发现要脱壳,手脱之后发现要iat修复,我就发现自己在这块知识缺失了,win逆向,好像一直都是打ctf,然后用逆向方法论去肝的 其他方面倒是没有很深入学习,但实际上 ...
- Linux学习之路(RPM和YUM)
rpm包的管理 介绍: 一种用于互联网下载包的打包及安装工具(类似windows中的setup).它包含在某些Linux分发版中.它生成具有RPM扩展名的文件.RPM是RedHat软件包管理工具缩写, ...
- Leetcode No.88 Merge Sorted Array(c++实现)
1. 题目 1.1 英文题目 You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and ...
- Mysql-5.7.28 Windows安装
1.下载mysql-5.7.28-winx64社区版并解压 2.解压后配置环境变量 3.my.ini配置文件及初始化mysql命令 4.登录mysql 每次windows安装mysql时都需要百度,自 ...
- linux驱动之LED驱动
通过之前的学习,了解到linux驱动编写的流程是:先通过注册函数注册我们编写的入口函数,然后在入口函数中获取设备号->注册字符设备->自动创建设备节点->获取设备树信息,最后通过销毁 ...
- 技能篇:docker的简易教程
虚拟机技术每家公司发展到一定规模都必须考虑的,更好的环境隔离,更好的事故排查,更好的服务部署 docker的原理 docker更换阿里源 docker容器的相关命令 Dockerfile文件编写 do ...
- webpack 快速入门 系列 —— 性能
其他章节请看: webpack 快速入门 系列 性能 本篇主要介绍 webpack 中的一些常用性能,包括热模块替换.source map.oneOf.缓存.tree shaking.代码分割.懒加载 ...
- MySQL用B+树(而不是B树)做索引的原因
众所周知,MySQL的索引使用了B+树的数据结构.那么为什么不用B树呢? 先看一下B树和B+树的区别. B树 维基百科对B树的定义为"在计算机科学中,B树(B-tree)是一种树状数据结构, ...
- ES6 Class类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类.class 的本质是 function.它可以看作一个语法糖,让对象原型的写法更加清晰.更像面向对象编程的语法类 ...