文中代碼示例工程以下,更多參考btrace和arthas:java
https://github.com/sayhiai/example-javaagent
複製代碼
5版本之後,jdk有一個包叫作instrument
,可以實現一些很是酷的功能。市面上一些APM
工具,就是經過它來進行的加強。git
這是基礎架構的必備技能,但對業務開發來講並非。許多面試會問到這個知識點,並非由於未來會用到,而是由於你說對jdk
比較熟悉,他想殺殺你的威風。github
不會用沒問題,但你要說不知道,就過度了點。面試
咱們一般的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
到某個進程進行運行時統計和調試,比較流行的btrace
和arthas
,底層就是這種技術。
大致分爲如下步驟:
javaagent最終的體現方式是一個jar包。使用idea建立一個默認的maven工程便可。
建立一個普通java類,添加premain
或者agentmain
方法,它們的參數徹底同樣。
此部分,要藉助額外jar包的功能。
實際的代碼邏輯須要實現ClassFileTransformer
接口。假如咱們要統計某個方法的執行時間。咱們使用javaassist
來加強字節碼,則能夠經過如下代碼來實現。
MainRun
類的字節碼實例hello
方法的字節碼實例_begin
,而後直接編寫代碼別忘了加入maven依賴
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.1-GA</version>
</dependency>
複製代碼
那麼咱們編寫的代碼是如何讓外界知曉呢?那就是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。
直接在啓動命令行中加入參數便可,在jvm啓動時啓用代理。
java -javaagent:agent.jar MainRun
複製代碼
在idea中,能夠將參數附着在jvm options裏。
接下來看一下測試代碼。
通常用在一些診斷工具上。使用jdk/lib/tools.jar中的功能,能夠動態的爲運行中的程序加入功能。主要有如下步驟:
通常,agent的jar包會以fatjar的方式提供,即將全部的依賴打包到一個大的jar包中。
若是你的功能複雜,依賴多,那麼這個jar包將會特別的大。
使用獨立的bom文件維護這些依賴是另一種方法。使用方自行管理依賴問題,但這一般會發生一些找不到jar包的錯誤。更糟糕的是,大多數在運行時才發現。
不要使用和jdk以及instrument包中相同的類名(包括包名),有時候你可以僥倖過關,但也會陷入沒法控制的異常中。
能夠看到,給系統動態的增長功能是很是酷的,但大多數狀況下很是耗費性能。你會發現,一些簡單的診斷工具,佔用你1核的cpu,是稀鬆日常的事情。
若是你用的jvm比較舊,頻繁的生成大量的代理類,會形成perm區的膨脹,容易發生OOM。
ClassLoader有雙親委派機制,若是你想要替換相應的類,必定要搞清楚它的類加載器應該用哪一個。不然替換的類,是不生效的哦。
將你的加強代碼,加入相似zk的主動通知功能,能夠經過管理後臺動態的調整應用的行爲。若是再集成一個相似groovy的腳本語言,理論上,你可以幹任何事情。
因此,使用-javaagent
參數引入的jar
包,或者使用attach
方式提供的一些診斷工具,小姐姐都不敢隨便使用。