四、Java探針技術(shù)

對于agent,是在vm啟動,執(zhí)行方法前,將字節(jié)碼修改的服務(wù)代理。?

對于javassist,是修改字節(jié)碼具體實現(xiàn)。

詳細(xì)原理可以參考:https://blog.csdn.net/ancinsdn/article/details/58276945?

最近面試阿里,面試官先是問我類加載的流程,然后問了個問題,能否在加載類的時候,對字節(jié)碼進(jìn)行修改

我懵逼了,答曰不知道,面試官說可以的,使用Java探針技術(shù),能夠?qū)崿F(xiàn)

我查了一下關(guān)于探針技術(shù)的知識:

基于javaAgent和Java字節(jié)碼注入技術(shù)的java探針工具技術(shù)原理

圖0-0:動態(tài)代理功能實現(xiàn)說明

我們利用javaAgent和ASM字節(jié)碼技術(shù)開發(fā)java探針工具,實現(xiàn)原理如下:

jdk1.5以后引入了javaAgent技術(shù),javaAgent是運行方法之前的攔截器。我們利用javaAgent和ASM字節(jié)碼技術(shù),在JVM加載class二進(jìn)制文件的時候,利用ASM動態(tài)的修改加載的class文件,在監(jiān)控的方法前后添加計時器功能,用于計算監(jiān)控方法耗時,同時將方法耗時及內(nèi)部調(diào)用情況放入處理器,處理器利用棧先進(jìn)后出的特點對方法調(diào)用先后順序做處理,當(dāng)一個請求處理結(jié)束后,將耗時方法軌跡和入?yún)ap輸出到文件中,然后根據(jù)map中相應(yīng)參數(shù)或耗時方法軌跡中的關(guān)鍵代碼區(qū)分出我們要抓取的耗時業(yè)務(wù)。最后將相應(yīng)耗時軌跡文件取下來,轉(zhuǎn)化為xml格式并進(jìn)行解析,通過瀏覽器將代碼分層結(jié)構(gòu)展示出來,方便耗時分析,如圖0-1所示。

圖0-1:java探針工具原理圖

Java探針工具功能點:

1、支持方法執(zhí)行耗時范圍抓取設(shè)置,根據(jù)耗時范圍抓取系統(tǒng)運行時出現(xiàn)在設(shè)置耗時范圍的代碼運行軌跡。

2、支持抓取特定的代碼配置,方便對配置的特定方法進(jìn)行抓取,過濾出關(guān)系的代碼執(zhí)行耗時情況。

3、支持APP層入口方法過濾,配置入口運行前的方法進(jìn)行監(jiān)控,相當(dāng)于監(jiān)控特有的方法耗時,進(jìn)行方法專題分析。

4、支持入口方法參數(shù)輸出功能,方便跟蹤耗時高的時候?qū)?yīng)的入?yún)?shù)。

5、提供WEB頁面展示接口耗時展示、代碼調(diào)用關(guān)系圖展示、方法耗時百分比展示、可疑方法凸顯功能。


下面看個例子:

第一篇:

JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。

JavaAgent 是運行在 main方法之前的攔截器,它內(nèi)定的方法名叫 premain ,也就是說先執(zhí)行 premain 方法然后再執(zhí)行 main 方法。

那么如何實現(xiàn)一個 JavaAgent 呢?很簡單,只需要增加 premain 方法即可。

看下面的代碼和代碼中的注釋說明:

先寫一個premain方法:

package agent;import java.lang.instrument.Instrumentation;publicclass pre_MyProgram {

? ? /**? ? * 該方法在main方法之前運行,與main方法運行在同一個JVM中

? ? * 并被同一個System ClassLoader裝載

? ? * 被統(tǒng)一的安全策略(security policy)和上下文(context)管理

? ? *

? ? * @param agentOps

? ? * @param inst

? ? * @author SHANHY

? ? * @create? 2016年3月30日

? ? */publicstaticvoid? ? premain(String agentOps,Instrumentation inst){


? ? ? ? System.out.println("====premain 方法執(zhí)行");

? ? ? ? System.out.println(agentOps);

? ? }


? ? /**? ? * 如果不存在 premain(String agentOps, Instrumentation inst)

? ? * 則會執(zhí)行 premain(String agentOps)

? ? *

? ? * @param agentOps

? ? * @author SHANHY

? ? * @create? 2016年3月30日

? ? */publicstaticvoid premain(String agentOps){


? ? ? System.out.println("====premain方法執(zhí)行2====");

? ? ? System.out.println(agentOps);

? }

? ? publicstaticvoid main(String[] args) {

? ? ? ? // TODO Auto-generated method stub?


? ? }

}


寫完這個類后,我們還需要做一步配置工作。

在 src 目錄下添加 META-INF/MANIFEST.MF 文件,內(nèi)容按如下定義:

Manifest-Version: 1.0

Premain-Class: agent.pre_MyProgram

Can-Redefine-Classes: true



要特別注意,一共是四行,第四行是空行,還有就是冒號后面的一個空格,如下截圖:

然后我們打包代碼為 pre_MyProgram.jar

注意打包的時候選擇我們自己定義的 MANIFEST.MF ,這是導(dǎo)出步驟:

(1)

(2) 注意選擇pre的MF文件

接著我們在創(chuàng)建一個帶有main方法的主程序工程,截圖如下:


這時候別忘了:

main函數(shù)也有MF文件:別寫錯了,不然導(dǎo)出報錯:No main manifest attribute(說明MF文件寫錯了)

Manifest-Version: 1.0Main-Class: alibaba.MyProgram




按同樣的方法導(dǎo)出main的jar包命名為:MyProgram.jar

?如下:


選擇它的MF文件:




如何執(zhí)行 MyProgram.jar ?我們通過 -javaagent 參數(shù)來指定我們的Java代理包,值得一說的是 -javaagent 這個參數(shù)的個數(shù)是不限的,如果指定了多個,則會按指定的先后執(zhí)行,執(zhí)行完各個 agent 后,才會執(zhí)行主程序的 main 方法。

命令如下:

C:\WINDOWS\system32>java -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre

_MyProgram.jar=Hello1 -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre_My

Program.jar=Hello2 -jar C:\Users\z003fe9c\Desktop\tessdata\agent\MyProgram.jar


輸出結(jié)果:?

====premain 方法執(zhí)行

Hello1====premain 方法執(zhí)行

Hello2=========main方法執(zhí)行====


特別提醒:

(1)如果你把 -javaagent 放在 -jar 后面,則不會生效。也就是說,放在主程序后面的 agent 是無效的。

比如執(zhí)行:

java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -jar myapp.jar -javaagent:G:\myagent.jar=Hello3

(2)如果main函數(shù)忘了選擇MF文件或是MF文件選擇的不對,就會報錯:


只會有前個生效,第三個是無效的。 ?

命令中的Hello1為我們傳遞給 premain 方法的字符串參數(shù)。

至此,我們會使用 javaagent 了,但是單單看這樣運行的效果,好像沒有什么實際意義嘛。

我們可以用 javaagent 做什么呢?下篇文章我們來介紹如何在項目中應(yīng)用 javaagent。

最后說一下,還有一種,在main方法執(zhí)行后再執(zhí)行代理的方法,因為不常用,而且主程序需要配置 Agent-Class,所以不常用,如果需要自行了解下 agentmain(String agentArgs, Instrumentation inst) 方法。


第二篇:

從此處開始,到最后,是我直接復(fù)制了其他人員的,因為我自己的一直沒有調(diào)試出來,不過思路清楚了:

第二篇可以直接看別人的?JavaAgent 應(yīng)用(spring-loaded 熱部署),以下的可以忽略掉:



上一篇文章簡單介紹了 javaagent ,想了解的可以移步 “JavaAgent

本文重點說一下,JavaAgent 能給我們帶來什么?

自己實現(xiàn)一個 JavaAgent xxxxxx

基于 JavaAgent 的 spring-loaded 實現(xiàn) jar 包的熱更新,也就是在不重啟服務(wù)器的情況下,使我們某個更新的 jar 被重新加載。

一、基于 JavaAgent 的應(yīng)用實例

JDK5中只能通過命令行參數(shù)在啟動JVM時指定javaagent參數(shù)來設(shè)置代理類,而JDK6中已經(jīng)不僅限于在啟動JVM時通過配置參數(shù)來設(shè)置代理類,JDK6中通過 Java Tool API 中的 attach 方式,我們也可以很方便地在運行過程中動態(tài)地設(shè)置加載代理類,以達(dá)到 instrumentation 的目的。

Instrumentation 的最大作用,就是類定義動態(tài)改變和操作。

最簡單的一個例子,計算某個方法執(zhí)行需要的時間,不修改源代碼的方式,使用Instrumentation 代理來實現(xiàn)這個功能,給力的說,這種方式相當(dāng)于在JVM級別做了AOP支持,這樣我們可以在不修改應(yīng)用程序的基礎(chǔ)上就做到了AOP,是不是顯得略吊。

創(chuàng)建一個 ClassFileTransformer 接口的實現(xiàn)類 MyTransformer

實現(xiàn) ClassFileTransformer 這個接口的目的就是在class被裝載到JVM之前將class字節(jié)碼轉(zhuǎn)換掉,從而達(dá)到動態(tài)注入代碼的目的。那么首先要了解MonitorTransformer 這個類的目的,就是對想要修改的類做一次轉(zhuǎn)換,這個用到了javassist對字節(jié)碼進(jìn)行修改,可以暫時不用關(guān)心jaavssist的原理,用ASM同樣可以修改字節(jié)碼,只不過比較麻煩些。

接著上一篇文章的2個工程,分別添加下面的類。

MyTransformer.java 添加到 MyAgent 工程中。

package com.shanhy.demo.agent;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.CtNewMethod;/** * 檢測方法的執(zhí)行時間

*

* @author 單紅宇(365384722)

* @myblog http://blog.csdn.net/catoop/ * @create 2016年3月30日

*/publicclassMyTransformerimplements ClassFileTransformer {

? ? finalstaticString prefix = "\nlong startTime = System.currentTimeMillis();\n";

? ? finalstaticString postfix = "\nlong endTime = System.currentTimeMillis();\n";

? ? // 被處理的方法列表finalstaticMap> methodMap =newHashMap>();

? ? public MyTransformer() {

? ? ? ? add("com.shanhy.demo.TimeTest.sayHello");

? ? ? ? add("com.shanhy.demo.TimeTest.sayHello2");

? ? }

? ? privatevoid add(String methodString) {

? ? ? ? String className = methodString.substring(0, methodString.lastIndexOf("."));

? ? ? ? String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);

? ? ? ? List list = methodMap.get(className);

? ? ? ? if(list ==null) {

? ? ? ? ? ? list =newArrayList();

? ? ? ? ? ? methodMap.put(className, list);

? ? ? ? }

? ? ? ? list.add(methodName);

? ? }

? ? @Override

? ? publicbyte[] transform(ClassLoader loader, String className, Class classBeingRedefined,

? ? ? ? ? ? ProtectionDomain protectionDomain, byte[] classfileBuffer)throws IllegalClassFormatException {

? ? ? ? className = className.replace("/", ".");

? ? ? ? if(methodMap.containsKey(className)) {// 判斷加載的class的包路徑是不是需要監(jiān)控的類CtClass ctclass =null;

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ctclass = ClassPool.getDefault().get(className);// 使用全稱,用于取得字節(jié)碼類<使用javassist>for (String methodName : methodMap.get(className)) {

? ? ? ? ? ? ? ? ? ? String outputStr = "\nSystem.out.println(\"this method " + methodName

? ? ? ? ? ? ? ? ? ? ? ? ? ? + " cost:\" +(endTime - startTime) +\"ms.\");";

? ? ? ? ? ? ? ? ? ? CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到這方法實例String newMethodName = methodName + "$old";// 新定義一個方法叫做比如sayHello$oldctmethod.setName(newMethodName);// 將原來的方法名字修改

? ? ? ? ? ? ? ? ? ? // 創(chuàng)建新的方法,復(fù)制原來的方法,名字為原來的名字CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass,null);

? ? ? ? ? ? ? ? ? ? // 構(gòu)建新的方法體StringBuilder bodyStr =new StringBuilder();

? ? ? ? ? ? ? ? ? ? bodyStr.append("{");

? ? ? ? ? ? ? ? ? ? bodyStr.append(prefix);

? ? ? ? ? ? ? ? ? ? bodyStr.append(newMethodName + "($$);\n");// 調(diào)用原有代碼,類似于method();($$)表示所有的參數(shù)? ? ? ? ? ? ? ? ? ? bodyStr.append(postfix);

? ? ? ? ? ? ? ? ? ? bodyStr.append(outputStr);

? ? ? ? ? ? ? ? ? ? bodyStr.append("}");

? ? ? ? ? ? ? ? ? ? newMethod.setBody(bodyStr.toString());// 替換新方法ctclass.addMethod(newMethod);// 增加新方法? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return ctclass.toBytecode();

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? System.out.println(e.getMessage());

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? returnnull;

? ? }

}


TimeTest.java 添加到 MyProgram 工程中。

package com.shanhy.demo;/** * 被測試類

*

* @author? 單紅宇(365384722)

* @myblog? http://blog.csdn.net/catoop/ * @create? ? 2016年3月30日

*/publicclass TimeTest {

? ? publicstaticvoid main(String[] args) {

? ? ? ? sayHello();

? ? ? ? sayHello2("hello world222222222");

? ? }

? ? publicstaticvoid sayHello() {

? ? ? ? try {

? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? System.out.println("hello world!!");

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? publicstaticvoid sayHello2(String hello) {

? ? ? ? try {

? ? ? ? ? ? Thread.sleep(1000);

? ? ? ? ? ? System.out.println(hello);

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

}


修改MyAgent.java 的 permain 方法,如下:

publicstaticvoid premain(String agentOps, Instrumentation inst) {

? ? ? ? System.out.println("=========premain方法執(zhí)行========");

? ? ? ? System.out.println(agentOps);

? ? ? ? // 添加Transformerinst.addTransformer(new MyTransformer());

? ? }


修改MANIFEST.MF內(nèi)容,增加 Boot-Class-Path 如下:

Manifest-Version: 1.0Premain-Class: com.shanhy.demo.agent.MyAgent

Can-Redefine-Classes:trueBoot-Class-Path: javassist-3.18.1-GA.jar


對2個工程分別打包為 myagent.jar 和 myapp.jar 然后將 javassist-3.18.1-GA.jar 和 myagent.jar 放在一起。

最后執(zhí)行命令測試,結(jié)果如下:

G:\>java -javaagent:G:\myagent.jar=Hello1 -jar myapp.jar=========premain方法執(zhí)行========Hello1

hello world!!this method sayHello cost:2000ms.

hello world222222222thismethod sayHello2 cost:1000ms.


二、使用 spring-loaded 實現(xiàn) jar 包熱部署

在項目開發(fā)中我們可以把一些重要但又可能會變更的邏輯封裝到某個 logic.jar 中,當(dāng)我們需要隨時更新實現(xiàn)邏輯的時候,可以在不重啟服務(wù)的情況下讓修改后的 logic.jar 被重新加載生效。

spring-loaded是一個開源項目,項目地址:https://github.com/spring-projects/spring-loaded

使用方法:

在啟動主程序之前指定參數(shù)

在啟動主程序之前指定參數(shù)-javaagent:C:/springloaded-1.2.5.RELEASE.jar -noverify


如果你想讓 Tomat 下面的應(yīng)用自動熱部署,只需要在 catalina.sh 中添加:

set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify


這樣就完成了 spring-loaded 的安裝,它能夠自動檢測Tomcat 下部署的webapps ,在不重啟Tomcat的情況下,實現(xiàn)應(yīng)用的熱部署。

通過使用 -noverify 參數(shù),關(guān)閉 Java 字節(jié)碼的校驗功能。

使用參數(shù) -Dspringloaded=verbose;explain;watchJars=tools.jar 指定監(jiān)視的jar (verbose;explain; 非必須),多個jar用“冒號”分隔,如 watchJars=tools.jar:utils.jar:commons.jar

當(dāng)然,它也有一些小缺限:

1. 目前官方提供的1.2.4 版本在linux上可以很好的運行,但在windows還存在bug,官網(wǎng)已經(jīng)有人提出:https://github.com/spring-projects/spring-loaded/issues/145

2. 對于一些第三方框架的注解的修改,不能自動加載,比如:spring mvc的@RequestMapping

3. log4j的配置文件的修改不能即時生效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文:Java探針-Java Agent技術(shù)利用JAVA探針分析復(fù)雜代碼運維實踐 總結(jié): 使用java代理來實現(xiàn)j...
    小小少年Boy閱讀 16,731評論 3 7
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,697評論 19 139
  • # 前言 在前文 探秘 Java 熱部署[http://m.itdecent.cn/p/731bc82933...
    莫那一魯?shù)?/span>閱讀 16,512評論 2 15
  • 懶人癌太嚴(yán)重了,哈哈,本來好久之前就準(zhǔn)備想做的事情,現(xiàn)在才開始??。 一直很想把做婚禮過程中的這份幸福感分享給大家,...
    NIU小丫閱讀 596評論 6 1
  • 剛過完青年節(jié),周末母親節(jié)就要來臨 每到母親節(jié)就會更加想起母親的愛和辛苦,上學(xué)的時候老師也讓寫過關(guān)于母親母愛的作文,...
    AladDn禹州1閱讀 177評論 0 0

友情鏈接更多精彩內(nèi)容