冷门instrument包,功能d炸天

文中代码示例工程以下,更多参考btrace和arthas:java

https://github.com/sayhiai/example-javaagent
复制代码

5版本之后,jdk有一个包叫作instrument,可以实现一些很是酷的功能。市面上一些APM工具,就是经过它来进行的加强。git

这是基础架构的必备技能,但对业务开发来讲并非。许多面试会问到这个知识点,并非由于未来会用到,而是由于你说对jdk比较熟悉,他想杀杀你的威风。github

不会用没问题,但你要说不知道,就过度了点。面试

javaagent介绍

咱们一般的java入口都是一个main方法,而javaagent的入口方法叫作premain,代表是在main运行以前的一些操做。javaagent就是一个jar包,定义了一个标准的premain()方法,并不须要继承或者实现任何其余的类。apache

这是一个约定,并木有什么其余的理由。这个方法,不管是第一次加载,仍是每次新的ClassLoader加载,都会执行。安全

咱们能够在这个前置的方法里,对字节码进行一些修改,来增长功能或者改变代码的行为。这种方法没有侵入性,只须要在启动命令中加上-javaagent参数就能够。Java6之后,甚至能够经过attach的方式,动态的给运行中的程序设置加载代理类。bash

有经验的同窗确定要提出异议了。其实,instrument有两个main方法,一个是premain,一个是agentmain,在一个JVM中,只会调用一个;前者是main执行以前的修改,后者控制类运行时的行为。它们仍是有一些区别的,agentmain由于比较危险,限制会更大一些。架构

有什么用

获取统计信息

许多apm产品,好比Pinpoint、SkyWalking等,就是使用javaagent对代码进行的加强。经过在方法执行先后动态加入的统计代码,进行监控信息的收集;经过兼容OpenTracing协议,能够实现分布式链路追踪的功能。 它的原理相似于aop,最终以字节码存在,性能损失取决于你的代码逻辑。jvm

热部署

经过自定义的ClassLoader,能够实现代码的热替换。使用agentmain,实现热部署功能会更加便捷。经过agentmain获取到Instrumentation之后,就能够对类进行动态重定义。maven

诊断

配合JVMTI技术,能够attach到某个进程进行运行时统计和调试,比较流行的btracearthas,底层就是这种技术。

如何作

大致分为如下步骤:

  • 构建agent jar包,编写加强代码
  • 在manifest中指定Premain-Class/Agent-Class属性
  • 使用参数加载或者attach方式使用

编写Agent

javaagent最终的体现方式是一个jar包。使用idea建立一个默认的maven工程便可。

建立一个普通java类,添加premain或者agentmain方法,它们的参数彻底同样。

编写Transformer

此部分,要借助额外jar包的功能。

实际的代码逻辑须要实现ClassFileTransformer接口。假如咱们要统计某个方法的执行时间。咱们使用javaassist来加强字节码,则能够经过如下代码来实现。

  • 获取MainRun类的字节码实例
  • 获取hello方法的字节码实例
  • 在方法先后,加入时间统计,首先定义变量_begin,而后直接编写代码

别忘了加入maven依赖

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.24.1-GA</version>
</dependency>
复制代码

字节码加强也可使用Cglib、asm等其余工具。

MANIFEST.MF文件

那么咱们编写的代码是如何让外界知晓呢?那就是MANIFEST.MF文件。具体路径在 src/main/resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
premain-class: com.sayhiai.example.javaagent.AgentApp
复制代码

通常的,maven打包会覆盖这个文件,因此咱们须要指定须要哪个。

<build><plugins><plugin>
<groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
            </archive>
    </configuration></plugin></plugins></build>
复制代码

而后,在命令行,执行mvn install安装到本地代码库,或者使用mvn deploy发布到私服上。

附,MANIFEST.MF参数清单: Premain-Class Agent-Class Boot-Class-Path Can-Redefine-Classes Can-Retransform-Classes Can-Set-Native-Method-Prefix

使用

使用方式取决于你使用的premain仍是agentmain。

premain

直接在启动命令行中加入参数便可,在jvm启动时启用代理。

java -javaagent:agent.jar MainRun
复制代码

在idea中,能够将参数附着在jvm options里。

接下来看一下测试代码。

这是咱们的执行类。执行后,直接输出hello world。经过加强之后,还额外的输出了执行时间,以及一些debug信息。其中,debug信息在main方法执行以前输出。

agentmain

通常用在一些诊断工具上。使用jdk/lib/tools.jar中的功能,能够动态的为运行中的程序加入功能。主要有如下步骤:

  • 获取机器上运行的全部jvm的进程id
  • 选择要诊断的jvm
  • 将jvm使用attach函数连接上
  • 使用loadAgent函数加载agent,动态修改字节码
  • 卸载jvm

这些代码都是比较危险的,这就是为何Btrace说了这么多年,仍是只在小范围内被当心使用。相对来讲,arthas显的友好并且安全的多。

注意点

1、jar包依赖方式

通常,agent的jar包会以fatjar的方式提供,即将全部的依赖打包到一个大的jar包中。

若是你的功能复杂,依赖多,那么这个jar包将会特别的大。

使用独立的bom文件维护这些依赖是另一种方法。使用方自行管理依赖问题,但这一般会发生一些找不到jar包的错误。更糟糕的是,大多数在运行时才发现。

2、类名称重复

不要使用和jdk以及instrument包中相同的类名(包括包名),有时候你可以侥幸过关,但也会陷入没法控制的异常中。

3、作有限的功能

能够看到,给系统动态的增长功能是很是酷的,但大多数状况下很是耗费性能。你会发现,一些简单的诊断工具,占用你1核的cpu,是稀松日常的事情。

4、ClassLoader

若是你用的jvm比较旧,频繁的生成大量的代理类,会形成perm区的膨胀,容易发生OOM。

ClassLoader有双亲委派机制,若是你想要替换相应的类,必定要搞清楚它的类加载器应该用哪一个。不然替换的类,是不生效的哦。

End

将你的加强代码,加入相似zk的主动通知功能,能够经过管理后台动态的调整应用的行为。若是再集成一个相似groovy的脚本语言,理论上,你可以干任何事情。

因此,使用-javaagent参数引入的jar包,或者使用attach方式提供的一些诊断工具,小姐姐都不敢随便使用。

相关文章
相关标签/搜索