日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

trace java_使用java动态字节码技术简单实现arthas的trace功能。

發(fā)布時(shí)間:2024/9/27 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 trace java_使用java动态字节码技术简单实现arthas的trace功能。 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

參考資料

用過(guò)[Arthas]的都知道,Arthas是alibaba開(kāi)源的一個(gè)非常強(qiáng)大的Java診斷工具。

不管是線上還是線下,我們都可以用Arthas分析程序的線程狀態(tài)、查看jvm的實(shí)時(shí)運(yùn)行狀態(tài)、打印方法的出入?yún)⒑头祷仡愋汀⑹占椒ㄖ忻總€(gè)代碼塊耗時(shí),

甚至可以監(jiān)控類、方法的調(diào)用次數(shù)、成功次數(shù)、失敗次數(shù)、平均響應(yīng)時(shí)長(zhǎng)、失敗率等。

前幾天學(xué)習(xí)java動(dòng)態(tài)字節(jié)碼技術(shù)時(shí),突然想起這款java診斷工具的trace功能:打印方法中每個(gè)節(jié)點(diǎn)的調(diào)用耗時(shí)。簡(jiǎn)簡(jiǎn)單單的,正好拿來(lái)做動(dòng)態(tài)字節(jié)碼入門學(xué)習(xí)的demo。

程序結(jié)構(gòu)

src

├── agent-package.bat

├── java

│ ├── asm

│ │ ├── MANIFEST.MF

│ │ ├── TimerAgent.java

│ │ ├── TimerAttach.java

│ │ ├── TimerMethodVisitor.java

│ │ ├── TimerTrace.java

│ │ └── TimerTransformer.java

│ └── demo

│ ├── MANIFEST.MF

│ ├── Operator.java

│ └── Test.java

├── run-agent.bat

├── target-package.bat

└── tools.jar

編寫目標(biāo)程序

代碼

package com.gravel.demo.test.asm;

/**

* @Auther: syh

* @Date: 2020/10/12

* @Description:

*/

public class Test {

public static boolean runnable = true;

public static void main(String[] args) throws Exception {

while (runnable) {

test();

}

}

// 目標(biāo):分析這個(gè)方法中每個(gè)節(jié)點(diǎn)的耗時(shí)

public static void test() throws Exception {

Operator.handler();

long time_wait = (long) ((Math.random() * 1000) + 2000);

Operator.callback();

Operator.pause(time_wait);

}

}

Operator.java

/**

* @Auther: syh

* @Date: 2020/10/28

* @Description: 輔助類,同樣可用于分析耗時(shí)

*/

public class Operator {

public static void handler() throws Exception {

long time_wait = (long) ((Math.random() * 10) + 20);

sleep(time_wait);

}

public static void callback() throws Exception {

long time_wait = (long) ((Math.random() * 10) + 20);

sleep(time_wait);

}

public static void pause(long time_wait) throws Exception {

sleep(time_wait);

}

public static void stop() throws Exception {

Test.runnable = false;

System.out.println("business stopped.");

}

private static void sleep(long time_wait) throws Exception {

Thread.sleep(time_wait);

}

}

MANIFEST.MF

編寫MANIFEST.MF文件,指定main-class。注意:冒號(hào)后面加空格,結(jié)尾加兩行空白行。

Manifest-Version: 1.0

Archiver-Version: Plexus Archiver

Built-By: syh

Created-By: Apache Maven

Build-Jdk: 1.8.0_202

Main-Class: com.gravel.demo.test.asm.Target

打包

偷懶寫了bat批命令,生成target.jar

@echo off & setlocal

attrib -s -h -r -a /s /d demo

rd /s /q demo

rd /q target.jar

javac -encoding utf-8 -d . ./java/demo/*.java

jar cvfm target.jar ./java/demo/MANIFEST.MF demo

rd /s /q demo

pause

java -jar target.jar

java agent探針

instrument 是 JVM 提供的一個(gè)可以修改已加載類文件的類庫(kù)。而要實(shí)現(xiàn)代碼的修改,我們需要實(shí)現(xiàn)一個(gè) instrument agent。

jdk1.5時(shí),agent有個(gè)內(nèi)定方法premain。是在類加載前修改。所以無(wú)法做到修改正在運(yùn)行的類。

jdk1.6后,agent新增了agentmain方法。agentmain是在虛擬機(jī)啟動(dòng)以后加載的。所以可以做攔截、熱部署等。

講JAVA探針技術(shù),實(shí)際上我自己也是半吊子。所以這里用的是邊分析別人例子邊摸索的思路來(lái)實(shí)現(xiàn)我的簡(jiǎn)單的trace功能。

例子使用的是ASM字節(jié)碼生成框架

MANIFEST.MF

首先一個(gè)可用的jar,關(guān)鍵之一是MAINFEST.MF文件是吧。

Manifest-Version: 1.0

Archiver-Version: Plexus Archiver

Created-By: Apache Maven

Built-By: syh

Build-Jdk: 1.8.0_202

Agent-Class: asm.TimerAgent

Can-Retransform-Classes: true

Can-Redefine-Classes: true

Class-Path: ./tools.jar

Main-Class: asm.TimerAttach

我們從MANIFEST.MF中提取幾個(gè)關(guān)鍵的屬性

屬性

說(shuō)明

Agent-Class

agentmain入口類

Premain-Class

premain入口類,與agent-class至少指定一個(gè)。

Can-Retransform-Classes

對(duì)于已經(jīng)加載的類重新進(jìn)行轉(zhuǎn)換處理,即會(huì)觸發(fā)重新加載類定義。

Can-Redefine-Classes

對(duì)已經(jīng)加載的類不做轉(zhuǎn)換處理,而是直接把處理結(jié)果(bytecode)直接給JVM

Class-Path

asm動(dòng)態(tài)字節(jié)碼技術(shù)依賴tools.jar,如果沒(méi)有可以從jdk的lib目錄下拷貝。

Main-Class

這里并不是agent的關(guān)鍵屬性,為了方便,我把加載虛擬機(jī)的程序和agent合并了。

代碼

然后我們來(lái)看看兩個(gè)入口類,首先分析一個(gè)可執(zhí)行jar的入口類Main-Class。

public class TimerAttach {

public static void main(String[] args) throws Exception {

/**

* 啟動(dòng)jar時(shí),需要指定兩個(gè)參數(shù):1目標(biāo)程序的pid。 2 要修改的類路徑及方法,格式 package.class#methodName

*/

if (args.length < 2) {

System.out.println("pid and class must be specify.");

return;

}

if (!args[1].contains("#")) {

System.out.println("methodName must be specify.");

return;

}

VirtualMachine vm = VirtualMachine.attach(args[0]);

// 這里為了方便我把 vm和agent整合在一個(gè)jar里面了, args[1]就是agentmain的入?yún)ⅰ?/p>

vm.loadAgent("agent.jar", args[1]);

}

}

代碼很簡(jiǎn)單,1:args入?yún)⑿r?yàn);2:加載目標(biāo)進(jìn)程pid(args[0]);3:加載agent jar包(因?yàn)楹喜⒘?#xff0c;所以這個(gè)jar其實(shí)就是自己)。

其中vm.loadAgent(agent.jar, args[1])會(huì)調(diào)用agent-class中的agentmain方法,而args[1]就是agentmain的第一個(gè)入?yún)ⅰ?/p>

public class TimerAgent {

public static void agentmain(String agentArgs, Instrumentation inst) {

String[] ownerAndMethod = agentArgs.split("#");

inst.addTransformer(new TimerTransformer(ownerAndMethod[1]), true);

try {

inst.retransformClasses(Class.forName(ownerAndMethod[0]));

System.out.println("agent load done.");

} catch (Exception e) {

e.printStackTrace();

System.out.println("agent load failed!");

}

}

}

在 agentmain 方法里,我們調(diào)用retransformClassess方法載入目標(biāo)類,調(diào)用addTransformer方法加載TimerTransformer類實(shí)現(xiàn)對(duì)目標(biāo)類的重新定義。

類轉(zhuǎn)換器

public class TimerTransformer implements ClassFileTransformer {

private String methodName;

public TimerTransformer(String methodName) {

this.methodName = methodName;

}

@Override

public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) {

ClassReader reader = new ClassReader(classFileBuffer);

ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

ClassVisitor classVisitor = new TimerTrace(Opcodes.ASM5, classWriter, methodName);

reader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

return classWriter.toByteArray();

}

}

對(duì)被匹配到的類中的方法進(jìn)行修改

public class TimerTrace extends ClassVisitor implements Opcodes {

private String owner;

private boolean isInterface;

private String methodName;

public TimerTrace(int i, ClassVisitor classVisitor, String methodName) {

super(i, classVisitor);

this.methodName = methodName;

}

@Override

public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {

super.visit(version, access, name, signature, superName, interfaces);

owner = name;

isInterface = (access & ACC_INTERFACE) != 0;

}

@Override

public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,

String[] exceptions) {

MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);

// 匹配到指定methodName時(shí),進(jìn)行字節(jié)碼修改

if (!isInterface && mv != null && name.equals(methodName)) {

// System.out.println(" package.className:methodName()")

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

mv.visitTypeInsn(NEW, "java/lang/StringBuilder");

mv.visitInsn(DUP);

mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);

mv.visitLdcInsn(" " + owner.replace("/", ".")

+ ":" + methodName + "() ");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",

"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

// 方法代碼塊耗時(shí)統(tǒng)計(jì)并打印

TimerMethodVisitor at = new TimerMethodVisitor(owner, access, name, descriptor, mv);

return at.getLocalVariablesSorter();

}

return mv;

}

public static void main(String[] args) throws IOException {

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

TraceClassVisitor tv = new TraceClassVisitor(cw, new PrintWriter(System.out));

TimerTrace addFiled = new TimerTrace(Opcodes.ASM5, tv, "test");

ClassReader classReader = new ClassReader("demo.Test");

classReader.accept(addFiled, ClassReader.EXPAND_FRAMES);

File file = new File("out/production/asm-demo/demo/Test.class");

String parent = file.getParent();

File parent1 = new File(parent);

parent1.mkdirs();

file.createNewFile();

FileOutputStream fileOutputStream = new FileOutputStream(file);

fileOutputStream.write(cw.toByteArray());

}

}

要統(tǒng)計(jì)方法中每行代碼耗時(shí),只需要在每一行代碼的前后加上當(dāng)前時(shí)間戳然后相減即可。

所以我們的代碼是這么寫的。

public class TimerMethodVisitor extends MethodVisitor implements Opcodes {

private int start;

private int end;

private int maxStack;

private String lineContent;

public boolean instance = false;

private LocalVariablesSorter localVariablesSorter;

private AnalyzerAdapter analyzerAdapter;

public TimerMethodVisitor(String owner, int access, String name, String descriptor, MethodVisitor methodVisitor) {

super(Opcodes.ASM5, methodVisitor);

this.analyzerAdapter = new AnalyzerAdapter(owner, access, name, descriptor, this);

localVariablesSorter = new LocalVariablesSorter(access, descriptor, this.analyzerAdapter);

}

public LocalVariablesSorter getLocalVariablesSorter() {

return localVariablesSorter;

}

/**

* 進(jìn)入方法后,最先執(zhí)行

* 所以我們可以在這里定義一個(gè)最開(kāi)始的時(shí)間戳, 然后創(chuàng)建一個(gè)局部變量var_end

* Long var_start = System.nanoTime();

* Long var_end;

*/

@Override

public void visitCode() {

mv.visitCode();

mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);

mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);

start = localVariablesSorter.newLocal(Type.LONG_TYPE);

mv.visitVarInsn(ASTORE, start);

end = localVariablesSorter.newLocal(Type.LONG_TYPE);

maxStack = 4;

}

/**

* 在每行代碼后面增加以下代碼

* var_end = System.nanoTime();

* System.out.println("[" + String.valueOf((var_end.doubleValue() - var_start.doubleValue()) / 1000000.0D) + "ms] " + "package.className:methodName() #lineNumber");

* var_start = var_end;

* @param lineNumber

* @param label

*/

@Override

public void visitLineNumber(int lineNumber, Label label) {

super.visitLineNumber(lineNumber, label);

if (instance) {

mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);

mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);

mv.visitVarInsn(ASTORE, end);

// System.out

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

// new StringBuilder();

mv.visitTypeInsn(NEW, "java/lang/StringBuilder");

mv.visitInsn(DUP);

mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);

mv.visitLdcInsn(" -[");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",

"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

mv.visitVarInsn(ALOAD, end);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "doubleValue", "()D", false);

mv.visitVarInsn(ALOAD, start);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "doubleValue", "()D", false);

mv.visitInsn(DSUB);

mv.visitLdcInsn(new Double(1000 * 1000));

mv.visitInsn(DDIV);

// String.valueOf((end - start)/1000000)

mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(D)Ljava/lang/String;", false);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",

"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

mv.visitLdcInsn("ms] ");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",

"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

// .append("owner:methodName() #line")

mv.visitLdcInsn(this.lineContent + "#" + lineNumber);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",

"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

// stringBuilder.toString()

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);

// println stringBuilder.toString()

mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

// start = end

mv.visitVarInsn(ALOAD, end);

mv.visitVarInsn(ASTORE, start);

maxStack = Math.max(analyzerAdapter.stack.size() + 4, maxStack);

}

instance = true;

}

/**

* 拼接字節(jié)碼內(nèi)容

* @param opcode

* @param owner

* @param methodName

* @param descriptor

* @param isInterface

*/

@Override

public void visitMethodInsn(int opcode, String owner, String methodName, String descriptor, boolean isInterface) {

super.visitMethodInsn(opcode, owner, methodName, descriptor, isInterface);

if (!isInterface && opcode == Opcodes.INVOKESTATIC) {

this.lineContent = owner.replace("/", ".")

+ ":" + methodName + "() ";

}

}

@Override

public void visitMaxs(int maxStack, int maxLocals) {

super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);

}

}

如果初學(xué)者不會(huì)改字節(jié)碼。可以利用idea自帶的asm插件做參考。

打包

這樣,一個(gè)可執(zhí)行的agent jar就寫完了,然后打包

@echo off

attrib -s -h -r -a /s /d asm

rd /s /q asm

rd /q agent.jar

javac -XDignore.symbol.file=true -encoding utf-8 -d . ./java/asm/*.java

jar cvfm agent.jar ./java/asm/MANIFEST.MF asm

rd /s /q asm

exit

測(cè)試

運(yùn)行目標(biāo)程序 target.jar

java -jar target.jar

打印Test.test中每個(gè)節(jié)點(diǎn)耗時(shí)

java -jar agent.jar [pid] demo.Test#test

結(jié)果

打印Operator.handler方法每個(gè)節(jié)點(diǎn)耗時(shí)

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

總結(jié)

以上是生活随笔為你收集整理的trace java_使用java动态字节码技术简单实现arthas的trace功能。的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 男男gay做受xx | 男人的天堂av网站 | 在线观看黄网 | 大尺码肥胖女系列av | 99久久99久久精品国产片桃花 | 日韩精品一区二区在线视频 | 农村少妇无套内谢粗又长 | 一级片网址 | 欧美夫妻性生活视频 | 亚洲av无码国产精品久久不卡 | 特黄在线 | 国产精品久久久久久久久免费桃花 | 理论片大全免费理伦片 | va婷婷| 激情小说在线 | 精品嫩模一区二区三区 | 亚洲欧美色图在线 | 黄色网日本 | 色偷偷噜噜噜亚洲男人的天堂 | 中文久久久 | avtt亚洲| 欧美又黑又粗 | 精品国产a | 亚洲高清影院 | 爱情岛论坛亚洲品质自拍视频 | 可以看的av网站 | 香蕉av一区二区三区 | 天堂在线精品视频 | 免费午夜人成电影 | 乱老熟女一区二区三区 | 中文字幕人妻一区二区 | 久久理论电影 | 亚洲偷拍一区 | 国产偷拍一区二区三区 | 久草福利资源在线观看 | 中文国产| 91成人免费在线观看视频 | 骚色综合 | 亚洲精选一区二区 | 懂色av色吟av夜夜嗨 | 欧美视频三区 | chien国产乱露脸对白 | 国产午夜福利视频在线观看 | 偷拍超碰| 九九热免费在线视频 | 欧美理论片在线观看 | 欧美成人免费视频 | 伊人av在线播放 | 久久免费看少妇高潮v片特黄 | 超碰pron| 欧美1区2区| 亚洲综合色在线观看 | 伊人五月天 | 一区二区三区四区在线免费观看 | 久久久久久久综合色一本 | 国产成人亚洲精品 | 国产一伦一伦一伦 | 精东传媒在线观看 | 国产视频123区 | 欧美xxxx性 | 黄视频在线 | 亚洲日本欧美在线 | 日本精品视频在线观看 | 姐姐你真棒插曲快来救救我电影 | 国产激情文学 | 亚洲+小说+欧美+激情+另类 | 国产盗摄精品一区二区酒店 | 国产av天堂无码一区二区三区 | 成人动漫在线观看免费 | 麻豆av免费 | 国产精品对白刺激久久久 | 天天干夜夜夜 | 激情综合五月天 | 国产精品无码av在线有声小说 | 亚洲一区二区中文字幕 | 久久96| 99久久久久久 | 福利片第一页 | 香蕉视频在线观看网站 | 蜜桃导航-精品导航 | 丁香花电影在线观看免费高清 | 国产+高潮+白浆+无码 | 日本乱论视频 | 快播日韩 | 九九福利视频 | chinese中国性按摩hd | 蜜桃视频在线入口www | 美日韩av | 女优在线观看 | 国产精品网站免费 | 少妇影院在线观看 | 一道本视频在线 | 富二代成人短视频 | 色爱综合 | 黄色福利视频网站 | 麻豆传媒在线免费 | 黄色成人在线免费观看 | 成人激情视频在线观看 | 日韩极品在线观看 |