日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1 背景

近日在給公司同事分享Arthas 工具使用時候,被它強悍的功能震撼到了就好奇研究了下它的原理及底層實現,其實它是通過JAVA agent 來實現的,也就深入地學習了一下Java agent 技術覺得蠻有意思,也得到了一些啟發,通過Java agent 我們可以做到很多意想不到的事情,在我們日常開發過程中你可能已經無形地接觸到了它,例如一些APM 工具如:pinpoint,skywalking,cat, arthas,BTrace... 甚至我們的IDEA debug工具無時無刻都有java agent 的身影...如果你是一個Java 開發者,你一定很有必要去研究一下它。


2 Java agent 介紹

Java agent 又名Java 探針活Java 代理,也有人稱它為 “插樁”,說的都是一個意思,只是大家給它起了一個比較通俗易懂的名字而已。“探針” 這個說法我感覺非常形象,JVM 一旦跑起來,對于外界來說,它就是一個黑盒子。而 Java Agent 可以像一支針一樣插到 JVM 內部,探到我們想要的東西,并且可以注入東西進去。就像我們生病時去醫院看醫生,醫生怎么診斷你身體的健康狀況呢,這時候往往借助一個聽診器,把它放在你的胸口去聽診,就能大概了解你的健康狀態了,怎么樣,是不是很形象? 那又如何理解代理呢?比方說我們需要了解目標 JVM 的一些運行指標,我們可以通過 Java Agent 來實現,這樣看來它就是一個代理的效果,我們最后拿到的指標是目標 JVM ,但是我們是通過 Java Agent 來獲取的,對于目標 JVM 來說,它就像是一個代理。

Java agent本質上可以理解為一個插件,該插件就是一個精心提供的jar包,這個jar包通過JVMTI(JVM Tool Interface)完成加載,最終借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成對目標代碼的修改。

2.1 java agent 技術的主要功能

java agent技術的主要功能如下:

  • 可以在加載java文件之前做攔截把字節碼做修改
  • 可以在運行期將已經加載的類的字節碼做變更
  • 還有其他的一些小眾的功能如:

獲取所有已經被加載過的類

獲取所有已經被初始化過了的類

獲取某個對象的大小

將某個jar加入到bootstrapclasspath里作為高優先級被bootstrapClassloader加載

將某個jar加入到classpath里供AppClassloard去加載

2.2 java Instrumentation API

通過java agent技術進行類的字節碼修改最主要使用的就是Java Instrumentation API。下面將介紹如何使用Java Instrumentation API進行字節碼修改。

有兩種方式拿到Instrumentation對象:

  1. jvm啟動時指定agent,Instrumentation對象會通過agent的premain方法傳遞。它是Java 5 開始提供的方式。
  2. jvm啟動后通過jvm提供的機制加載agent,Instrumentation對象會通過agent的agentmain方法傳遞。它是java6 開始提供的方式

Java Agent支持目標JVM啟動時加載,也支持在目標JVM運行時加載,這兩種不同的加載模式會使用不同的入口函數,如果需要在目標JVM啟動的同時加載Agent,那么可以選擇實現下面的方法:

[1] public static void premain(String agentArgs, Instrumentation inst); 
[2] public static void premain(String agentArgs);

JVM將首先尋找[1],如果沒有發現[1],再尋找[2]。如果希望在目標JVM運行時加載Agent,則需要實現下面的方法:

[1] public static void agentmain(String agentArgs, Instrumentation inst); 
[2] public static void agentmain(String agentArgs);

這兩組方法的第一個參數AgentArgs是隨同 “–javaagent”一起傳入的程序參數,如果這個字符串代表了多個參數,就需要自己解析這些參數。inst是Instrumentation類型的對象,是JVM自動傳入的,我們可以拿這個參數進行類增強等操作。

 

3 兩個小demo

下面我將用兩種方式分別對premain 和 agentmain 兩種方式介紹。

簡單起見,我就對我的目標程序做一個耗時統計,在方法體前后聲明兩個變量并計算耗時。

3.1 premain 的方式

這里隱藏了一些公司的敏感的信息用“xxx” 代替

我的pom文件

<plugin>
   <groupId>org.Apache.maven.plugins</groupId>
   <artifactId>maven-jar-plugin</artifactId>
   <configuration>
      <archive>
         <manifest>
            <addClasspath>true</addClasspath>
         </manifest>
         <manifestEntries>
            <Premain-Class>com.xxx.xxx.xxx.PreMainAgent</Premain-Class>
         </manifestEntries>
      </archive>
   </configuration>
</plugin>
package com.xxx.xxx.capital;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

/**
 * Created on 2021/9/5 10:07 下午. <br/>
 * Description: <br/>
 * description for class template.
 *
 * @author danniel.l
 */
public class PreMainAgent {

    private static Instrumentation instrumentation;
    public static void premain(String agentArgs, Instrumentation inst) {
        instrumentation = inst;
        System.err.println("我在main啟動之前啟動");

        inst.addTransformer(new MyTransformer());
    }

}
package com.xxx.xxx.capital;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

/**
 * Created on 2021/9/5 11:49 下午. <br/>
 * Description: <br/>
 * description for class template.
 *
 * @author danniel.l
 */
public class MyTransformer implements ClassFileTransformer {

    final static String prefix = "nlong startTime = System.currentTimeMillis();n";
    final static String postfix = "nlong endTime = System.currentTimeMillis();n";

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer){

        if (!className.startsWith("com/xxx/xxx/xxx/agenttest")) {
            return null;
        }

        className = className.replace("/", ".");
        CtClass ctclass = null;
        try {
            ctclass = ClassPool.getDefault().get(className);// 使用全稱,用于取得字節碼類<使用javassist>
            for(CtMethod ctMethod : ctclass.getDeclaredMethods()){
                String methodName = ctMethod.getName();
                String newMethodName = methodName + "$old";// 新定義一個方法叫做比如sayHello$old
                ctMethod.setName(newMethodName);// 將原來的方法名字修改

                // 創建新的方法,復制原來的方法,名字為原來的名字
                CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctclass, null);

                // 構建新的方法體
                StringBuilder bodyStr = new StringBuilder();
                bodyStr.append("{");
                bodyStr.append("System.out.println("==============Enter Method: " + className + "." + methodName + " ==============");");
                bodyStr.append(prefix);
                bodyStr.append(newMethodName + "($$);n");// 調用原有代碼,類似于method();($$)表示所有的參數
                bodyStr.append(postfix);
                bodyStr.append("System.out.println("==============Exit Method: " + className + "." + methodName + " Cost:" +(endTime - startTime) +"ms " + "===");");
                bodyStr.append("}");

                newMethod.setBody(bodyStr.toString());// 替換新方法
                ctclass.addMethod(newMethod);// 增加新方法
            }
            return ctclass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}


目標增強的類

package com.xxx.xxx.xxx.agenttest;

import org.springframework.web.bind.annotation.RestController;

/**
 * Created on 2021/9/6 12:20 上午. <br/>
 * Description: <br/>
 * description for class template.
 *
 * @author danniel.l
 */
@RestController
public class AgentTest {

    public void test1(){
        System.out.println("this is test1");
    }

    public void test2(){
        System.out.println("this is test2");
    }


}

 

啟動時增加如下參數

-javaagent:/Users/user/IdeaProjects/bc/xxx-xxx/xxx-xxx-web/target/xxx-xxx-web-2.0.0-SNAPSHOT.jar

 

3.2 agentmain 的方式

3.2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>agent-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.24.0-GA</version>
        </dependency>

        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8.0</version>
            <scope>system</scope>
            <systemPath>/Library/Java/JavaVirtualmachines/jdk1.8.0_291.jdk/Contents/Home/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

    <build>
        <finalName>agentmain-test</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Agent-Class>agent.test.myagent.MyAgentTransformer</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.2.2

package agent.test.target;

/**
 * Created on 2021/9/7 3:24 下午. <br/>
 * Description: <br/>
 * 需要增強的目標應用程序入口
 *
 * @author danniel.l
 */
public class TargetMain {

    public static void main(String[] args) {
        TargetTest targetTest = new TargetTest();
        while (true) {
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
            targetTest.test();
            targetTest.method();
        }
    }

}

 

package agent.test.target;
/**
 * Created on 2021/9/7 3:25 下午. <br/>
 * Description: <br/>
 * 需要增強的目標應用程序類
 *
 * @author danniel.l
 */

public class TargetTest {

    public void test(){
        System.out.println("this is a test!");
    }

    public void method() {
        System.err.println("this is a method!");
    }
}

 

package agent.test.myagent;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

/**
 * Created on 2021/9/7 3:44 下午. <br/>
 * Description: <br/>
 * Agent 程序入口.
 *
 * @author danniel.l
 */
public class MyAgentMain {

    public static void main(String[] args) throws Exception {
        // 需要增強的目標程序的名稱,可根據args 參數傳遞進來
        String targetApplicationName = "TargetMain";

        // 需要增強的目標類,可根據args 參數傳遞進來
        String targetClassName = "agent.test.target.TargetTest";

        // 獲取本機已啟動的應用程序名稱集合列表
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vmd : list) {
            if (vmd.displayName().endsWith(targetApplicationName)) {
                // 通過VirtualMachine.attach() 附著上目標程序
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());

                // 把探針代理程序插樁到目標程序里去。。
                virtualMachine.loadAgent("/Users/user/IdeaProjects/test/agent-test/target/agentmain-test.jar", targetClassName);
                System.out.println("Attached target application successfully!");
                virtualMachine.detach();
            }
        }
    }

}

 

package agent.test.myagent;

import agent.test.target.TargetTest;
import javassist.*;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

/**
 * Created on 2021/9/5 10:30 下午. <br/>
 * Description: <br/>
 * Agent 增強邏輯處理.
 *
 * @author danniel.l
 */
public class MyAgentTransformer {

    public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException {
        System.out.println("agentmain starts...."+ agentArgs);
        instrumentation.addTransformer(new Transformer(agentArgs), true);

        // 允許修改TargetTest類
        instrumentation.retransformClasses(TargetTest.class);
    }

    private static class Transformer implements ClassFileTransformer {
        private final String targetClassName;

        public Transformer(String targetClassName) {
            this.targetClassName = targetClassName;
        }

        @Override
        public byte[] transform(ClassLoader loader,
                                String className,
                                Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain,
                                byte[] classfileBuffer) {
            if (className == null) {
                return null;
            }

            className = className.replace("/", ".");
            if (!className.equals(targetClassName)) {
                return null;
            }
            System.out.println("transform className=: " + className);

            ClassPool classPool = ClassPool.getDefault();

            // 將要修改的類的classpath加入到ClassPool中,否則找不到該類
            classPool.appendClassPath(new LoaderClassPath(loader));
            try {
                CtClass ctClass = classPool.get(className);
                for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
                    if (Modifier.isPublic(ctMethod.getModifiers()) && !ctMethod.getName().equals("test")) {
                        // 修改字節碼
                        ctMethod.addLocalVariable("begin", CtClass.longType);
                        ctMethod.addLocalVariable("end", CtClass.longType);

                        ctMethod.insertBefore("begin = System.currentTimeMillis();");
                        ctMethod.insertAfter("Thread.sleep(1000L);");
                        ctMethod.insertAfter("end = System.currentTimeMillis();");

                        ctMethod.insertAfter("System.out.println("方法" + ctMethod.getName() + "耗時"+ (end - begin) +"ms");");
                    }
                }
                ctClass.detach();
                return ctClass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return classfileBuffer;
        }
    }

}

整個目錄結構如下

Java 探針技術原理及實踐

 

 

先執行TargetMain 這個目標程序啟動效果如下:

Java 探針技術原理及實踐

 

再啟動MyAgentMain 代理程序

 

Java 探針技術原理及實踐

 

 

最后再回到TargetMain 效果已經出來了

 

Java 探針技術原理及實踐

 

分享到:
標簽:探針 Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定