冷門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方式提供的一些診斷工具,小姐姐都不敢隨便使用。

相關文章
相關標籤/搜索