性能分析
对于动态代理的各种实现,其性能各不相同。找到了一篇对于各个实现方式性能测试的文章 —— 《动态代理方案性能对比》。
组件版本
更新了各组件到最新版本:
- JDK: 1.8.0_102
- ASM: 4.2
- CGLIB: 3.1
- JAVAASSIST: 3.12.1.GA
测试结果
性能测试结果如下:
Create JDK Proxy: 14 ms
Create CGLIB Proxy: 186 ms
Create JAVAASSIST Proxy: 104 ms
Create JAVAASSIST Bytecode Proxy: 160 ms
Create ASM Proxy: 3 ms
================================================
Run JDK Proxy: 105 ms, 13,429,194 t/s
Run CGLIB Proxy: 195 ms, 7,231,104 t/s
Run JAVAASSIST Proxy: 691 ms, 2,040,615 t/s
Run JAVAASSIST Bytecode Proxy: 102 ms, 13,824,170 t/s
Run ASM Bytecode Proxy: 127 ms, 11,102,877 t/s
------------------------------------------------
Run JDK Proxy: 419 ms, 3,365,311 t/s
Run CGLIB Proxy: 94 ms, 15,000,695 t/s
Run JAVAASSIST Proxy: 1053 ms, 1,339,093 t/s
Run JAVAASSIST Bytecode Proxy: 62 ms, 22,742,990 t/s
Run ASM Bytecode Proxy: 82 ms, 17,195,919 t/s
------------------------------------------------
Run JDK Proxy: 140 ms, 10,071,895 t/s
Run CGLIB Proxy: 92 ms, 15,326,797 t/s
Run JAVAASSIST Proxy: 528 ms, 2,670,578 t/s
Run JAVAASSIST Bytecode Proxy: 81 ms, 17,408,214 t/s
Run ASM Bytecode Proxy: 73 ms, 19,315,964 t/s
结果分析
测试结论
- ASM和JAVAASSIST字节码生成方式不相上下,都很快,是CGLIB的5倍。
- CGLIB次之,是JDK自带的两倍。
- JDK自带的再次之,因JDK1.6对动态代理做了优化,如果用低版本JDK更慢,要注意的是JDK也是通过字节码生成来实现动态代理的,而不是反射。
- JAVAASSIST提供者动态代理接口最慢,比JDK自带的还慢。 (这也是为什么网上有人说JAVAASSIST比JDK还慢的原因,用JAVAASSIST最好别用它提供的动态代理接口,而可以考虑用它的字节码生成方式)
差异原因
各方案生成的字节码不一样, 像JDK和CGLIB都考虑了很多因素,以及继承或包装了自己的一些类, 所以生成的字节码非常大,而我们很多时候用不上这些, 而手工生成的字节码非常小,所以速度快, 具体的字节码对比,后面有贴出,可自行分析。
最终选型
最终决定使用JAVAASSIST的字节码生成代理方式, 虽然ASM稍快,但并没有快一个数量级, 而JAVAASSIST的字节码生成方式比ASM方便, JAVAASSIST只需用字符串拼接出Java源码,便可生成相应字节码, 而ASM需要手工写字节码。
测试源码
CountService.java
public interface CountService {
int count();
}
CountServiceImpl.java
public class CountServiceImpl implements CountService {
private int count = 0;
@Override
public int count() {
return count ++;
}
}
DynamicProxyPerformanceTest.java
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DecimalFormat;
public class DynamicProxyPerformanceTest {
public static void main(String[] args) throws Exception {
CountService delegate = new CountServiceImpl();
long time = System.currentTimeMillis();
CountService jdkProxy = createJdkDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JDK Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService cglibProxy = createCglibDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create CGLIB Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistProxy = createJavassistDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create ASM Proxy: " + time + " ms");
System.out.println("================================================");
for (int i = 0; i < 3; i++) {
test(jdkProxy, "Run JDK Proxy: ");
test(cglibProxy, "Run CGLIB Proxy: ");
test(javassistProxy, "Run JAVAASSIST Proxy: ");
test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");
test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");
System.out.println("------------------------------------------------");
}
}
private static void test(CountService service, String label)
throws Exception {
service.count(); // warm up
int count = 10000000;
long time = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
service.count();
}
time = System.currentTimeMillis() - time;
System.out.println(label + time + " ms, " + new DecimalFormat().format(count * 1000 / time) + " t/s");
}
private static CountService createJdkDynamicProxy(final CountService delegate) {
CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{CountService.class}, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
return method.invoke(delegate, objects);
}
}
private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{CountService.class});
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(new Class[]{CountService.class});
Class proxyClass = proxyFactory.createClass();
CountService javassistProxy = (CountService) proxyClass.newInstance();
((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
return javassistProxy;
}
private static class JavaAssitInterceptor implements MethodHandler {
final Object delegate;
JavaAssitInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable {
return m.invoke(delegate, args);
}
}
private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(CountService.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
Class pc = mCtc.toClass();
CountService bytecodeProxy = (CountService) pc.newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
String className = CountService.class.getName() + "AsmProxy";
String classPath = className.replace('.', '/');
String interfacePath = CountService.class.getName().replace('.', '/');
classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});
MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
initVisitor.visitCode();
initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
initVisitor.visitInsn(Opcodes.RETURN);
initVisitor.visitMaxs(0, 0);
initVisitor.visitEnd();
FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
fieldVisitor.visitEnd();
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
methodVisitor.visitInsn(Opcodes.IRETURN);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] code = classWriter.toByteArray();
CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static class ByteArrayClassLoader extends ClassLoader {
public ByteArrayClassLoader() {
super(ByteArrayClassLoader.class.getClassLoader());
}
public synchronized Class getClass(String name, byte[] code) {
if (name == null) {
throw new IllegalArgumentException("");
}
return defineClass(name, code, 0, code.length);
}
}
}
参考资料
文档信息
- 本文作者:Bob.Zhu
- 本文链接:https://adolphor.github.io/2016/12/14/java-proxy-performance/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)