`
ayufox
  • 浏览: 273251 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

[Java性能剖析]JPDA 4)Java Instrutment

    博客分类:
  • JVM
阅读更多

      在前面的介绍中,我们看到JVM TI的强大功能,然而,对于不熟悉C/C++语言的Java程序员来说,这扇门是不是真的就完全关闭了呢?还好,在关上了门的同时,JVM为我们提供了另一扇窗——Java Instrutment。其实Java Instrutment只提供了JVM TI中非常小的一个功能子集,一个是允许在类加载之前,修改类字节(ClassFileTransformer)(JDK5中开始提供,即使随JVM启动的Agent),另外一个是在类加载之后,触发JVM重新进行类加载(JDK6中开始提供,用于JVM启动之后通过Attach去加载Agent)。这两个功能表面看起来微不足道,但实际非常强大,AspectJ AOP的动态Weaving、Visual VM的性能剖析、JConsole支持Attach到进程上进行监控,都是通过这种方式来做的。除了这两个功能外,JDK 6中还提供了动态增加BootstrapClassLoader/SystemClassLoader的搜索路径、对Native方法进行instrutment(还记得JVM TI的Native Method Bind吗?)。
      1.主要API(java.lang.instrutment)
      1)ClassFileTransformer:定义了类加载前的预处理类,可以在这个类中对要加载的类的字节码做一些处理,譬如进行字节码增强
      2)Instrutmentation:增强器,由JVM在入口参数中传递给我们,提供了如下的功能

  • addTransformer/ removeTransformer:注册/删除ClassFileTransformer
  • retransformClasses:对于已经加载的类重新进行转换处理,即会触发重新加载类定义,需要注意的是,新加载的类不能修改旧有的类声明,譬如不能增加属性、不能修改方法声明
  • redefineClasses:与如上类似,但不是重新进行转换处理,而是直接把处理结果(bytecode)直接给JVM
  • getAllLoadedClasses:获得当前已经加载的Class,可配合retransformClasses使用
  • getInitiatedClasses:获得由某个特定的ClassLoader加载的类定义
  • getObjectSize:获得一个对象占用的空间,包括其引用的对象
  • appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch:增加BootstrapClassLoader/SystemClassLoader的搜索路径
  • isNativeMethodPrefixSupported/setNativeMethodPrefix:支持拦截Native Method

      我们后面通过范例来了解如何使用Java Instrutment技术。
      2.随JVM启动的Agent
      1)定义入口

public class StubPreMain
{
    //另外一种入口格式是public static void premain(String agentArgs)
    public static void premain(String agentArgs, Instrumentation inst)
            throws ClassNotFoundException, UnmodifiableClassException
    {
        inst.addTransformer(new StubTransformer());
        System.out.println("StubPreMain:Add StubTransformer");
    }
}

      我们的入口类格式必须是如上的premain(对应JVM TI的Agent_OnLoad方法),与JVM TI类似,JVM启动的时候回回调这个入口函数。在premain中我们最常见就是增加Transform,Transform允许我们在类加载器修改bytecode。一般性能剖析程序都是通过修改字节码,在方法进入和退出时收集时间数据来得出剖析数据的。
      2)编写Transform

public class StubTransformer implements ClassFileTransformer
{
    /*
     * (non-Javadoc)
     *
     * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader,
     *      java.lang.String, java.lang.Class, java.security.ProtectionDomain,
     *      byte[])
     */
    public byte[] transform(ClassLoader loader, String className,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
            byte[] classfileBuffer) throws IllegalClassFormatException
    {
        if ("ray.WebStub".equals(className))
        {
            System.out.println("Load WebStub From Transformer");

            return readByte("WebStub.classbyte");
        }
        return null;
    }

    public byte[] readByte(String fileName)
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        InputStream is = StubTransformer.class
                .getResourceAsStream(fileName);
        int BUFFER_SIZE = 1024;
        byte[] buffer = new byte[BUFFER_SIZE];
        int length = BUFFER_SIZE;
        while (length == BUFFER_SIZE)
        {
            try
            {
                length = is.read(buffer);
                baos.write(buffer, 0, length);
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }

        return baos.toByteArray();
    }
}

      这里简单地使用一个编译好的新的class的字节替换掉旧的class的字节,实际中我们当然不会使用这么苯的方法,实际上有各种字节码操作工具可以辅助我们完成这个任务

      3)修改META-INF/ MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: ray.StubPreMain

      4)打成stubagent.jar,在启动参数中增加如下启动参数,启动JVM,当加载ray.WebStub这个类的时候,字节码会变成我们传进去的新的字节码

-javaagent:D:/Workspace/agent/stubagent.jar

      3.通过Attach在JVM运行期间加载的Agent
      这种Agent是JDK6才开始支持的,因此使用一般还是比较少的,我们来看一个实际使用范例,在前面的JVM Management API 的介绍中,我们知道,通过Attach API连接到JVM上,然后加载management-agent.jar,就可以在JVM中启动一个JMX代理。实际上management-agent.jar是一个支持通过Attach在JVM运行期间加载的Agent。management-agent.jar实际上只有一个META-INF/ MANIFEST.MF文件,内容如下:

Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Agent-Class: sun.management.Agent
Premain-Class: sun.management.Agent

      可以看出这个包实际上既支持随JVM一起启动(我们前面看到的Premain-Class配置),也支持在JVM启动后通过Attach API去加载启动(看Agent-Class配置),我们通过反汇编看看sun.management.Agent是怎么做的。
sun.management.Agent定义了入口函数agentmain(对应于Agent_OnAttach方法)

public class Agent
{
   public static void premain(String agentArgs)
        throws Exception
    {
        agentmain(s);
    }

    public static void agentmain(String agentArgs)
        throws Exception
    {
          ….
    }
}

       与Premian-Agetn类似,另外一种入口函数格式是public static void premain(String agentArgs, Instrumentation inst)。

4
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics