- 浏览: 891568 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
zzuliuli:
很实用,一直关注
mysql的执行计划 -
rxin2009:
你好,最近在解决redis数据同步的问题,找到了tedis,但 ...
taobao/tedis的redis集群 -
zhangping2056:
楼主接下来要考虑页面静态化与细节上面的东西了
Nginx与Redis解决高并发问题 -
XieFuQ:
Tomcat的重启shell脚本 -
jovinlee:
jovinlee 写道 jov ...
Tomcat的重启shell脚本
什么是Instrumentation?
java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。 Java SE5中使用JVM TI替代了JVM PI和JVM DI。提供一套代理机制,支持独立于JVM应用程序之外的程序以代理的方式连接和访问JVM。java.lang.instrument是在JVM TI的基础上提供的Java版本的实现。 Instrumentation提供的主要功能是修改jvm中类的行为。 Java SE6中由两种应用Instrumentation的方式,premain(命令行)和agentmain(运行时)
premain方式
在Java SE5时代,Instrument只提供了premain一种方式,即在真正的应用程序(包含main方法的程序)main方法启动前启动一个代理程序。例如使用如下命令:
java -javaagent:agent_jar_path[=options] java_app_name
可以在启动名为java_app_name的应用之前启动一个agent_jar_path指定位置的agent jar。实现这样一个agent jar包,必须满足两个条件:
- 在这个jar包的manifest文件中包含Premain-Class属性,并且改属性的值为代理类全路径名。
- 代理类必须提供一个public static void premain(String args, Instrumentation inst)或 public static void premain(String args) 方法。
当在命令行启动该代理jar时,VM会根据manifest中指定的代理类,使用于main类相同的系统类加载器(即ClassLoader.getSystemClassLoader()获得的加载器)加载代理类。在执行main方法前执行premain()方法。如果premain(String args, Instrumentation inst)和premain(String args)同时存在时,优先使用前者。其中方法参数args即命令中的options,类型为String(注意不是String[]),因此如果需要多个参数,需要在方法中自己处理(比如用”;”分割多个参数之类);inst是运行时由VM自动传入的Instrumentation实例,可以用于获取VM信息。
premain实例-打印所有的方法调用
下面实现一个打印程序执行过程中所有方法调用的功能,这个功能可以通过AOP其他方式实现,这里只是尝试使用Instrumentation进行ClassFile的字节码转换实现:
构造agent类
premain方式的agent类必须提供premain方法,代码如下:
package test;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String args, Instrumentation inst){
System.out.println("Hi, I'm agent!");
inst.addTransformer(new TestTransformer());
}
}
premain有两个参数,args为自定义传入的代理类参数,inst为VM自动传入的Instrumentation实例。 premain方法的内容很简单,除了标准输出外,只有
inst.addTransformer(new TestTransformer());
这行代码的意思是向inst中添加一个类的转换器。用于转换类的行为。
构造Transformer
下面来实现上述过程中的TestTransformer来完成打印调用方法的类定义转换。
package test;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public class TestTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader arg0, String arg1, Class<?> arg2,
ProtectionDomain arg3, byte[] arg4)
throws IllegalClassFormatException {
ClassReader cr = new ClassReader(arg4);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
for (Object obj : cn.methods) {
MethodNode md = (MethodNode) obj;
if ("<init>".endsWith(md.name) || "<clinit>".equals(md.name)) {
continue;
}
InsnList insns = md.instructions;
InsnList il = new InsnList();
il.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System",
"out", "Ljava/io/PrintStream;"));
il.add(new LdcInsnNode("Enter method-> " + cn.name+"."+md.name));
il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
insns.insert(il);
md.maxStack += 3;
}
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
}
}
TestTransformer实现了ClassFileTransformer接口,该接口只有一个transform方法,参数传入包括该类的类加载器,类名,原字节码字节流等,返回被转换后的字节码字节流。 TestTransformer主要使用ASM实现在所有的类定义的方法中,在方法开始出添加了一段打印该类名和方法名的字节码。在转换完成后返回新的字节码字节流。详细的ASM使用请参考ASM手册。
设置MANIFEST.MF
设置MANIFEST.MF文件中的属性,文件内容如下:
Manifest-Version: 1.0
Premain-Class: test.Agent
Created-By: 1.6.0_29
测试
代码编写完成后将代码编译打成agent.jar。编写测试代码:
public class TestAgent {
public static void main(String[] args) {
TestAgent ta = new TestAgent();
ta.test();
}
public void test() {
System.out.println("I'm TestAgent");
}
}
从命令行执行该类,并设置agent.jar
java -javaagent:agent.jar TestAgent
将打印出程序运行过程中实际执行过的所有方法名:
Hi, I'm agent!
Enter method-> test/TestAgent.main
Enter method-> test/TestAgent.test
I'm TestAgent
Enter method-> java/util/IdentityHashMap$KeySet.iterator
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
Enter method-> java/util/IdentityHashMap$KeyIterator.next
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
Enter method-> java/util/IdentityHashMap$KeySet.iterator
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
Enter method-> java/util/IdentityHashMap$KeyIterator.next
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
Enter method-> com/apple/java/Usage$3.run
。。。
从输出中可以看出,程序首先执行的是代理类中的premain方法(不过代理类自身不会被自己转换,所以不能打印出代理类的方法名),然后是应用程序中的main方法。
agentmain方式
premain时Java SE5开始就提供的代理方式,给了开发者诸多惊喜,不过也有些须不变,由于其必须在命令行指定代理jar,并且代理类必须在main方法前启动。因此,要求开发者在应用前就必须确认代理的处理逻辑和参数内容等等,在有些场合下,这是比较苦难的。比如正常的生产环境下,一般不会开启代理功能,但是在发生问题时,我们不希望停止应用就能够动态的去修改一些类的行为,以帮助排查问题,这在应用启动前是无法确定的。为解决运行时启动代理类的问题,Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:
- 在manifest中指定Agent-Class属性,值为代理类全路径
- 代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。
不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。
Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。具体需要参考《Attach API》
agentmain实例-打印当前已加载的类
构造agent类
agentmain方式的代理类必须提供agentmain方法:
package loaded;
import java.lang.instrument.Instrumentation;
public class LoadedAgent {
@SuppressWarnings("rawtypes")
public static void agentmain(String args, Instrumentation inst){
Class[] classes = inst.getAllLoadedClasses();
for(Class cls :classes){
System.out.println(cls.getName());
}
}
}
agentmain方法通过传入的Instrumentation实例获取当前系统中已加载的类。
设置MANNIFEST.MF
设置MANIFEST.MF文件,指定Agent-Class:
Manifest-Version: 1.0
Agent-Class: loaded.LoadedAgent
Created-By: 1.6.0_29
绑定到目标VM
将agent类和MANIFEST.MF文件编译打成loadagent.jar后,由于agent main方式无法向pre main方式那样在命令行指定代理jar,因此需要借助Attach Tools API。
package attach;
import java.io.IOException;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
public class Test {
public static void main(String[] args) throws AttachNotSupportedException,
IOException, AgentLoadException, AgentInitializationException {
VirtualMachine vm = VirtualMachine.attach(args[0]);
vm.loadAgent("/Users/jiangbo/Workspace/code/java/javaagent/loadagent.jar");
}
}
该程序接受一个参数为目标应用程序的进程id,通过Attach Tools API的VirtualMachine.attach方法绑定到目标VM,并向其中加载代理jar。
构造目标测试程序
构造一个测试用的目标应用程序:
package attach;
public class TargetVM {
public static void main(String[] args) throws InterruptedException{
while(true){
Thread.sleep(1000);
}
}
}
这个测试程序什么都不做,只是不停的sleep。:) 运行该程序,获得进程ID=33902。运行上面绑定到VM的Test程序,将进程id作为参数传入:
java attach.Test 33902
观察输出,会打印出系统当前所有已经加载类名
java.lang.NoClassDefFoundError
java.lang.StrictMath
java.security.SignatureSpi
java.lang.Runtime
java.util.Hashtable$EmptyEnumerator
sun.security.pkcs.PKCS7
java.lang.InterruptedException
java.io.FileDescriptor$1
java.nio.HeapByteBuffer
java.lang.ThreadGroup
[Ljava.lang.ThreadGroup;
java.io.FileSystem
。。。
参考文档
附:agent jar中manifest的属性
- Premain-Class: 当在VM启动时,在命令行中指定代理jar时,必须在manifest中设置Premain-Class属性,值为代理类全类名,并且该代理类必须提供premain方法。否则JVM会异常终止。
- Agent-Class: 当在VM启动之后,动态添加代理jar包时,代理jar包中manifest必须设置Agent-Class属性,值为代理类全类名,并且该代理类必须提供agentmain方法,否则无法启动该代理。
- Boot-Class-Path: Bootstrap class loader加载类时的搜索路径,可选。
- Can-Redefine-Classes: true/false;标示代理类是否能够重定义类。可选。
- Can-Retransform-Classes: true/false;标示代理类是否能够转换类定义。可选。
- Can-Set-Native-Prefix::true/false;标示代理类是否需要本地方法前缀,可选。
** 当一个代理jar包中的manifest文件中既有Premain-Class又有Agent-Class时,如果以命令行方式在VM启动前指定代理jar,则使用Premain-Class;反之如果在VM启动后,动态添加代理jar,则使用Agent-Class **
发表评论
-
静态代理之AspectJ编译织入
2019-10-17 08:41 0前面两篇文章都是说的在代码运行时动态的生成class文件达到 ... -
ReentrantLock(重入锁)功能详解和应用演示
2019-10-15 09:54 0https://www.cnblogs.com/taku ... -
Java技术之AQS详解
2019-10-15 09:04 0https://www.jianshu.com/p/da9d ... -
基于AQS实现的Java并发工具类
2019-10-15 09:02 0https://www.cnblogs.com/booths ... -
java线程池ThreadPoolExecutor类使用详解
2019-10-10 17:06 0在《阿里巴巴java开发手册》中指出了线程资源必须通 ... -
java如何实现按指定行读取文件
2019-05-24 18:30 0最近在开发实战中,遇到了一个这样的技术情景: 把 ... -
通过反射把map中的属性赋值到实体类bean对象中
2019-04-12 13:38 0使用过struts2后感觉最方便的就是这个框架能自动把表单 ... -
使用slf4j + Log4j2构建日志
2017-04-07 10:42 0一、背景 Log4j 1.x 在高并发情况下出现死锁导致c ... -
Apache Log4j 2.0值得升级吗
2017-02-08 10:46 0Apache软件基金会最近发布了Log4j 2.0通用版本, ... -
maven deploy到nexus报错:Return code is: 401, ReasonPhrase:Unauthorized
2017-01-16 15:05 0正确 mvn deploy:deploy-file -D ... -
Java四种线程池的使用
2016-12-08 11:10 0Java通过Executors提供四种线程池,分别为:new ... -
Maven类包冲突终极解决小技若干
2016-08-19 14:34 765那句话怎么讲来着的... 引用 如果你爱他,就请让他用 ... -
Linux下Mysql 5.6.21 tar包安装实践
2016-07-25 14:58 3994http://blog.csdn.net/zhanngle ... -
Java8 Lambda表达式教程
2016-06-21 13:36 724http://blog.csdn.net/ioriogami ... -
加密机
2015-12-09 09:57 4758什么是加密机? 加密机是通过国家商用密码主管部门鉴定并批 ... -
maven setting
2015-11-25 16:26 0<?xml version="1.0&quo ... -
SPRING中的线程池ThreadPoolTaskExecutor
2015-11-04 18:20 6062一、初始化 1,直接调用 [java] ... -
解决Maven中使用很多本地jar包的编译问题
2015-10-28 16:58 5395Maven依赖本地非repository中的jar包,依赖j ... -
Spring线程池开发实战及使用spring注解
2015-10-27 11:03 2774作者:chszs,转载需注明。 作者博客主页:http:/ ... -
利用反射调用方法抛出的异常如何被捕获?
2015-10-13 14:41 0我们通常在java开发中采用自定义异常,在业务中遇到 ...
相关推荐
maven-resources-production java.lang.NegativeArraySizeException java.lang.NegativeArraySizeException 问题解决
java.lang.NoSuchFieldError: Companion 问题的解决方案
"Java.lang.OutOfMemoryError: Java heap space 解决方法" Java.lang.OutOfMemoryError: Java heap space 是 Java 中的一个常见错误,它发生时,Java 虚拟机 (JVM) 无法分配对象,因为堆空间不足。下面是解决该问题...
idea启动项目报错 java.lang.NegativeArraySizeException解决方法
### Java 错误处理:java.lang.OutOfMemoryError: Java heap space 在Java应用程序开发过程中,经常遇到的一个问题就是内存溢出错误,特别是在处理大量数据或长时间运行的应用时。其中,“java.lang....
标题中的问题“scrcpy投屏 AssertionError: java.lang.reflect.InvocationTargetException”是用户在尝试使用Scrcpy时遇到的一个常见错误。这个错误通常意味着在执行某个方法时,Java运行时环境遇到了未预期的情况。...
标题 "java.lang.Exception: java.lang.IllegalArgumentException: firstMovedIndex, lastMove" 描述了一个Java编程中的异常情况。这个异常通常发生在尝试执行一个不合法的操作时,例如数组或集合操作超出了其边界。...
Java程序在运行过程中可能会遇到各种异常,其中"nested exception is java.lang.OutOfMemoryError: Java heap space"是一个常见的问题,通常发生在程序试图分配超过堆内存限制的空间时。这个错误表明Java虚拟机(JVM...
java.lang.OutOfMemoryError处理错误 java.lang.OutOfMemoryError是Java虚拟机(JVM)中的一种常见错误,发生这种错误时,JVM将无法继续运行,程序将崩溃。这种错误的出现通常是由于Jvm的内存不足或内存泄露导致的...
在Java编程中,`java.lang.StackOverflowError` 是一个常见的运行时异常,它通常发生在程序执行过程中,当Java虚拟机(JVM)的调用栈溢出时。调用栈是每个线程用来存储方法调用信息的数据结构,当递归调用过深或者...
解决 java.lang.NoSuchFieldError: STRING at org.jbpm.identity.hibernate.PermissionUserType. 不用jbpm的jbpm-identity.jar 用这个就好
Java中的`java.lang.UnsatisfiedLinkError`是一个常见的运行时异常,通常出现在Java试图加载本地(C或C++)库但找不到相应的库文件时。这个错误可能是由于多种原因引起的,如库路径设置不正确、库文件不存在或者版本...
### java.lang.UnsupportedClassVersionError问题解析与解决方案 在Java开发过程中,经常会在部署或运行时遇到`java.lang.UnsupportedClassVersionError`错误。该错误通常发生在类文件版本与JVM(Java虚拟机)版本...
解决 java.lang.RuntimeException: Could not generate DH keypair异常处理。 bcprov-ext-jdk15on-1.60、bcprov-jdk15on-1.60两个包放到jre下的$JAVA_HOME/jre/lib/ext的路径下,然后配置$JAVA_HOME/jre/lib/...
java.lang.NoClassDefFoundError: de/javakaffee/kryoserializers/CurrencySerializer
nested exception is java.lang.NoClassDefFoundError_kmode exception" 指出的问题,是Java开发中常见的错误,通常发生在运行时。这个错误表明系统在尝试执行某个类时找不到对应的类定义。`NoClassDefFoundError` ...
java.lang.OutOfMemoryError: PermGen space 解决方案
### java.lang.UnsupportedClassVersionError问题的解决方法 在开发Java应用程序的过程中,经常会遇到与JDK版本不兼容的问题,其中一种常见的异常就是`java.lang.UnsupportedClassVersionError`。该错误通常发生在...
在Java编程中,`java.lang.IllegalArgumentException` 是一个标准的运行时异常,它通常表示一个方法接收到的参数值不在预期范围内或者不合法。当尝试将一个无法转换为日期的对象格式化时,就会抛出"Cannot format ...