gpt4 book ai didi

java - 使用 Java 代理快速仅转换一个类

转载 作者:行者123 更新时间:2023-12-02 02:23:56 25 4
gpt4 key购买 nike

我想在不产生大量开销的情况下测量服务器的启动时间。

我实际上想要测量的是从服务器进程执行到服务器开始监听已知端口的时间。

例如,我想测量一个简单的Netty Server的启动时间。即从启动到准备好接受请求的时间。

我使用 Byte-Buddy 开发了一个 Java 代理。

public class Agent {

public static void premain(String arg, Instrumentation instrumentation) {
new AgentBuilder.Default()
.type(ElementMatchers.named("io.netty.bootstrap.AbstractBootstrap"))
.transform((builder, typeDescription, classLoader, javaModule) ->
builder.visit(Advice.to(TimeAdvice.class)
.on(ElementMatchers.named("bind").and(ElementMatchers.takesArguments(SocketAddress.class)))))
.installOn(instrumentation);
}
}

以下是TimeAdvice的源代码

public class TimeAdvice {

@Advice.OnMethodExit
static void exit(@Advice.Origin String method) {
System.out.println(String.format("Server started. Current Time (ms): %d", System.currentTimeMillis()));
System.out.println(String.format("Server started. Current Uptime (ms): %d",
ManagementFactory.getRuntimeMXBean().getUptime()));
}
}

使用此代理,启动时间约为 1400 毫秒。但我通过修改服务器代码来测量启动时间,服务器的启动时间在650ms左右。

因此,在考虑启动时间时,byte-buddy Java 代理似乎有相当大的开销。

我还尝试了另一个带有 Javassist 的 Java 代理。

public class Agent {

private static final String NETTY_CLASS = "io/netty/bootstrap/AbstractBootstrap";

public static void premain(String arg, Instrumentation instrumentation) {
instrumentation.addTransformer((classLoader, s, aClass, protectionDomain, bytes) -> {
if (NETTY_CLASS.equals(s)) {
System.out.println(aClass);
long start = System.nanoTime();
// Javassist
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("io.netty.bootstrap.AbstractBootstrap");
CtMethod m = cc.getDeclaredMethod("bind", new CtClass[]{cp.get("java.net.SocketAddress")});
m.insertAfter("{ System.out.println(\"Server started. Current Uptime (ms): \" + " +
"java.lang.management.ManagementFactory.getRuntimeMXBean().getUptime());}");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
System.out.println(String.format("Agent - Transformation Time (ms): %d", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
}
}

return null;
});
}
}

使用此代理,启动时间约为 800 毫秒。

如何最大限度地减少开销并测量启动时间?有没有办法直接改造特定的类,而不需要遍历所有的类?如果我可以直接改造一个类,我想我应该能够尽可能地减少开销。

最佳答案

由于您处于 premain 中,因此您可能会测量许多以前未使用过的类的加载和初始化时间。当应用程序第一次使用它们时,很有可能稍后会加载和初始化大量这些类,而不会被测量为“启动时间”,因此将此时间移至测量的启动时间时间可能不是一个实际问题。

请注意,您在这两个变体中都使用了 lambda 表达式,这会导致 JRE 提供的后端初始化。对于 OpenJDK,它在底层使用 ASM,但由于它已被重新打包以避免与使用 ASM 的应用程序发生冲突,因此它与 Byte-Buddy 内部使用的类不同,因此您需要在这里付出两次初始化 ASM 的代价.

如上所述,如果无论如何都会使用这些类,即如果应用程序稍后将使用 lambda 表达式或方法引用,则您不必担心这一点,因为“优化”只会将初始化移至稍后的时间。但是,如果应用程序不使用 lambda 表达式或方法引用,或者您想不惜一切代价从测量的启动时间中删除此时间跨度,您可以诉诸普通的接口(interface)实现,使用内部类或让 Agent 实现接口(interface)。

为了进一步减少启动时间,您可以直接使用 ASM,跳过 Byte-Buddy 类的初始化,例如

import java.lang.instrument.*;
import java.lang.management.ManagementFactory;
import java.security.ProtectionDomain;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

public class Agent extends ClassVisitor implements ClassFileTransformer {
private static final String NETTY_CLASS = "io/netty/bootstrap/AbstractBootstrap";

public static void premain(String arg, Instrumentation instrumentation) {
instrumentation.addTransformer(new Agent());
}

public Agent() {
super(Opcodes.ASM5);
}
public byte[] transform(ClassLoader loader, String className, Class<?> cl,
ProtectionDomain pd, byte[] classfileBuffer) {
if(!NETTY_CLASS.equals(className)) return null;

ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, 0);
synchronized(this) {
super.cv = cw;
try { cr.accept(this, 0); }
finally { super.cv = null; }
}
return cw.toByteArray();
}

@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {

MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if(name.equals("bind")
&& desc.equals("(Ljava/net/SocketAddress;)Lio/netty/channel/ChannelFuture;")) {
return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
@Override
protected void onMethodExit(int opcode) {
super.visitMethodInsn(Opcodes.INVOKESTATIC,
Agent.class.getName().replace('.', '/'),
"injectedMethod", "()V", false);
super.onMethodExit(opcode);
}
};
}
return mv;
}
public static void injectedMethod() {
System.out.printf("Server started. Current Time (ms): %d",
System.currentTimeMillis());
System.out.printf("Server started. Current Uptime (ms): %d",
ManagementFactory.getRuntimeMXBean().getUptime());
}
}

(未测试)

显然,这段代码比使用 Byte-Buddy 的代码更复杂,因此您必须决定进行哪种权衡。

ASM 已经非常轻量级了。更深入意味着仅使用 ByteBufferHashMap 进行类文件转换;这是可能的,但肯定不是你想要走的路......

关于java - 使用 Java 代理快速仅转换一个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48072166/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com