目录 1、反射的运行速度 1.1测试赋值/取值基本的方法调用 1.2分析慢的原因 1.3反射调用的主要过程 1.3.1加载类 1.3.2创建实例 1.3.3获取方法 1.3.4调用方法 1.4改进 1.4.1缓存 1.4.2还有哪里可以改进? 1.4.3结论 2、java反射的实现 2.1使用javassist生成代理类 3、解决方案 4、遇到的问题
/**功能说明:性能研究,直接访问对象字段
* 主要确认反射速度慢的原因
* @author:linghushaoxia
* @time:2017年4月29日下午3:19:11
* @version:1.0
* 为中国羸弱的技术撑起一片自立自强的天空
*/
public class ReflectionPerformance {
public static void main(String[] args) {
VisitDirect.visitDitectMain();
}
}
/**
* 现实就是实现理想的过程
*/
/**
*
* 功能说明:直接访问测试
* @time:2017年4月29日下午3:40:54
* @author:linghushaoxia
* @exception:
*
*/
public static void visitDitectMain(){
/**
* 访问次数
* 100万次
*/
long count=1000000L;
/**
* 开始测试
*/
long start = System.nanoTime();
for(long i=0;i<count;i++){
visitDitect();
}
long end = System.nanoTime();
System.out.println("直接访问耗时:"+(end-start)/1000000+"ms");
}
/**
*
* 功能说明:直接访问对象字段
* @time:2017年4月29日下午3:29:39
* @author:linghushaoxia
* @exception:
*
*/
public static void visitDitect(){
Universe universe = new Universe();
/**
* 赋值
*/
universe.setAge(1000000000000000000L);
universe.setCoordinate("UN-1");
universe.setExpand(true);
universe.setRadius(10000000);
/**
*取值
*/
universe.getAge();
universe.getCoordinate();
universe.getRadius();
}
反射访问,测试代码如下
/**功能说明:性能研究,反射访问字段
* 主要确认反射速度慢的原因
* @author:linghushaoxia
* @time:2017年4月29日下午3:19:11
* @version:1.0
* 为中国羸弱的技术撑起一片自立自强的天空
*/
public class ReflectionPerformance {
public static void main(String[] args) {
VisitRefelct.visitReflectMain();
}
}
/**
* 现实就是实现理想的过程
*/
/**
*
* 功能说明:反射访问测试
* @time:2017年4月29日下午3:40:54
* @author:linghushaoxia
* @exception:
*
*/
public static void visitReflectMain(){
//访问次数,100万次
long count=1000000L;
/**
* 开始测试
*/
long start = System.nanoTime();
//计时起始索引值
int countStart=20;
String className = "com.linghushaoxia.javabase.reflection.research.Universe";
try {
Class<?> universeClass =Class.forName(className);
for(long i=0;i<count;i++){
//为了测试缓存效果,从第二次开始计时
if (countStart==i) {
start = System.nanoTime();
}
visitRefelct(universeClass);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("反射访问耗时:"+(end-start)/1000000+"ms");
}
/**功能说明:性能研究,缓存反射方法和class对象
* 主要确认反射速度慢的原因
* @author:linghushaoxia
* @time:2017年4月29日下午3:19:11
* @version:1.0
* 为中国羸弱的技术撑起一片自立自强的天空
*/
public class ReflectionPerformance {
public static void main(String[] args) {
VisitRefelct.visitReflectMain();
}
}
/**
* 现实就是实现理想的过程
*/
/**
*
* 功能说明:反射访问测试
* @time:2017年4月29日下午3:40:54
* @author:linghushaoxia
* @exception:
*
*/
public static void visitReflectMain(){
//访问次数,100万次
long count=1000000L;
/**
* 开始测试
*/
long start = System.nanoTime();
//计时起始索引值
int countStart=20;
String className = "com.linghushaoxia.javabase.reflection.research.Universe";
try {
Class<?> universeClass =ReflectUtil.getClass(className);
for(long i=0;i<count;i++){
//为了测试缓存效果,从第二次开始计时
if (countStart==i) {
start = System.nanoTime();
}
visitRefelctCache(universeClass);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("反射访问耗时:"+(end-start)/1000000+"ms");
}
反射工具类,ReflectUtil
/**功能说明:反射工具类,缓存方法和class对象
* @author:linghushaoxia
* @time:2017年4月30日下午4:18:30
* @version:1.0
* 为中国羸弱的技术撑起一片自立自强的天空
*/
public class ReflectUtil {
//类缓存
private static Map<String, Class<?>> classCache = new ConcurrentHashMap<String, Class<?>>();
//方法缓存
private static Map<MethodProperty, Method> methodCache = new ConcurrentHashMap<MethodProperty, Method>();
/**
*
* 功能说明:获取class对象
* @param className
* 类名称
* @return Class<?>
* @time:2017年4月30日下午5:04:46
* @author:linghushaoxia
* @exception:
*
*/
public static Class<?> getClass(String className){
//返回结果
Class<?> classResult=null;
//先从缓存获取
if (classCache.containsKey(className)) {
classResult= classCache.get(className);
}else {
//新加载
try {
classResult = Class.forName(className);
classCache.put(className, classResult);
} catch (Exception e) {
e.printStackTrace();
}
}
return classResult;
}
/**
*
* 功能说明:获取方法
* @param className
* 类名
* @param methodName
* 方法名
* @param params
* 参数类型列表
* @return Method
* @time:2017年4月30日下午5:06:30
* @author:linghushaoxia
* @exception:
*
*/
public static Method getMethod(String className,String methodName,Class<?>... params) throws SecurityException{
//返回结果
Method methodResult=null;
MethodProperty methodProperty = new MethodProperty(className,methodName, params);
if (methodCache.containsKey(methodProperty)) {
methodResult = methodCache.get(methodProperty);
}else {
try {
methodResult = getClass(className).getDeclaredMethod(methodName, params);
methodResult.setAccessible(true);
methodCache.put(methodProperty, methodResult);
} catch (Exception e) {
e.printStackTrace();
}
}
return methodResult;
}
/**功能说明:方法属性描述
* @author:linghushaoxia
* @time:2017年4月30日下午4:27:39
* @version:1.0
* 为中国羸弱的技术撑起一片自立自强的天空
*/
public class MethodProperty {
//类名
private String className;
//方法名
private String methodName;
//方法参数
private Class<?>[] params;
public MethodProperty(){
}
public MethodProperty(String className ,String methodName,Class<?> ... params){
this.className=className;
this.methodName=methodName;
this.params = params;
}
@Override
public boolean equals(Object object){
if (object==null) {
return false;
}
if (this==object) {
return true;
}else {
if (object instanceof MethodProperty) {
MethodProperty methodProperty = (MethodProperty)object;
//方法名相同
if (className!=null&&methodProperty.getClassName()!=null
&&className.equals(methodProperty.getClassName())
&&methodName!=null&&methodProperty.getMethodName()!=null
&&methodName.equals(methodProperty.getMethodName())) {
if (params==null&&methodProperty.getParams()==null) {
return true;
}else {
if (params!=null&&methodProperty.getParams()!=null&¶ms.length==methodProperty.getParams().length) {
for(int i=0;i<params.length;i++){
if (params[i]!=methodProperty.getParams()[i]) {
return false;
}
}
return true;
}
}
}
}
}
return false;
}
//hash值的求解方法可以改善
@Override
public int hashCode(){
int result=19;
//类名
result=className.hashCode()*result+methodName.hashCode();
//参数的hash值
if (params!=null) {
for (Class<?> class1 : params) {
if (class1.getName()!=null) {
result = result*class1.getName().hashCode();
}
}
}
return result;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParams() {
return params;
}
public void setParams(Class<?>[] params) {
this.params = params;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
return "{\""
+ (className != null ? "className\":\"" + className + "\",\""
: "")
+ (methodName != null ? "methodName\":\"" + methodName
+ "\",\"" : "")
+ (params != null ? "params\":\"" + Arrays.toString(params)
: "") + "\"}";
}
}
/**
* 现实就是实现理想的过程
*/
加上缓存后,性能有5-10倍的改善。
因为使用到HashMap,对key求hash值还存在改善的空间,然而改善的空间不大。
1.4.2还有哪里可以改进?
目前调用过程invoke,还没没有研究。看一下java的invoke执行过程
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
从代码中看,主要涉及到如下过程
Method.invoke--->check(Reflection.quickCheckMemberAccess,AccessibleObject.checkAccess)---->获取MethodAccessor
---->执行调用invoke
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
优化结果
// NOTE that there is no synchronization used here. It is correct
// (though not efficient) to generate more than one MethodAccessor
// for a given Method. However, avoiding synchronization will
// probably make the implementation more scalable.
private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
首次调用时,会执行reflectionFactory.newMethodAccessor(this)
源码如下
public MethodAccessor newMethodAccessor(Method method) {
checkInitted();
if (noInflation) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
获取MethodAccessor的实例逻辑主要分为两种,通过标志noInflation,来判断是否增大到一定值,来选择逻辑分支.
查看noInflation的注释
//
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
"通胀"机制。在前几次调用中,加载字节码实现方法调用和生成实例,通常比本地代码花费3-4倍的时间(虽然后续的调用经过检测速度快乐20倍以上)。
不幸的是,这个花费增加了某些使用反射引导自己的应用的启动时间,为了避免这个缺点,在前几次的方法和构造器调用中,我们重用了已经存在的jvm的进入点
然后切换到基于字节码的实现中。
MethodAccessorGenerator是使用字节码生成技术,生成java代码版本的MethodAccessor,而NativeMethodAccessorImpl是native版本的MethodAccessor
查看NativeConstructorAccessorImpl,生成实例和方法调用的逻辑都会对,调用次数自增,并对临界值进行校验
可以看到调用NativeConstructorAccessorImpl.newInstance
NativeMethodAccessorImpl.invoke源码如下
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException{
//次数达到一定程度,使用字节码生成代理
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
//调用native方法
return invoke0(method, obj, args);
}
现在清楚了,获取到的MethodAccessor的实现,分为两套一个是native版本的,一套是java版本的,第16次开始调用java版本的,而且java版本的性能会随着调用越来越高,
最终会快于native版本的20倍以上。
3)MethodAccessor.invoke
方法的正常调用,是调用方法DelegatingMethodAccessorImpl.invoke,调用他内部的delegate.invoke
在NativeMethodAccessorImpl的版本中,达到阈值调用MethodAccessorGenerator.generateMethod会生成java版本的代理方法
到这里,是不是可以测试下效率低是不是native版本的问题?可以把计时起始次数调整到16跳过native版本的逻辑
经过测试,无明显改善。
1.4.3结论
经过以上的测试,得出以下结论
1)java的反射实现,性能低于直接调用,反射调用的时间成本是直接调用的10倍以上;
2)对造成性能低的原因,进行了推测和验证,结论是基于做出的改进,对性能几乎没有提升。缓存是有明显的提升的,提升约10倍.
那么,就只说明java版本的反射实现,效率也低。
按照道理讲,java语言本身调用方法,应该很快,应该和直接调用相当。为什么这么低?
public MethodAccessor newMethodAccessor(Method method) {
checkInitted();
if (noInflation) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
DelegatingMethodAccessorImpl在运行时,会将反射的实现由native版本切换至字节码版本,
字节码版本的是什么样子的呢?这里给出一个字节码生成的实例
测试类,一个普通java类
public class Business {
public void invoke(java.lang.String p1,int p2,Long p3){
System.out.println("p1="+p1);
System.out.println("p2="+p2);
System.out.println("p3="+p3);
}
}
java生成的字节码如下
import com.linghushaoxia.javabase.reflection.research.Business;
import java.lang.reflect.InvocationTargetException;
import sun.reflect.MethodAccessorImpl;
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public Object invoke(Object arg0, Object[] arg1)
throws InvocationTargetException {
if (arg0 == null) {
throw new NullPointerException();
} else {
String arg10000;
Business arg9999;
Long arg10002;
int arg10001;
try {
arg9999 = (Business) arg0;
if (arg1.length != 3) {
throw new IllegalArgumentException();
}
arg10000 = (String) arg1[0];
Object arg2 = arg1[1];
if (arg2 instanceof Byte) {
arg10001 = ((Byte) arg2).byteValue();
} else if (arg2 instanceof Character) {
arg10001 = ((Character) arg2).charValue();
} else if (arg2 instanceof Short) {
arg10001 = ((Short) arg2).shortValue();
} else {
if (!(arg2 instanceof Integer)) {
throw new IllegalArgumentException();
}
arg10001 = ((Integer) arg2).intValue();
}
arg10002 = (Long) arg1[2];
} catch (NullPointerException | ClassCastException arg4) {
throw new IllegalArgumentException(arg4.toString());
}
try {
arg9999.invoke(arg10000, arg10001, arg10002);
return null;
} catch (Throwable arg3) {
throw new InvocationTargetException(arg3);
}
}
}
}
可以看到,java为目标方法生成了一个类,该类持有目标对象的实例,对参数经过校验,转换直接调用目标对象的方法
使用代理的方式进行访问,这段代码和直接访问的区别在哪里?
前期需要生成代理类,耗费些时间,可以加入缓存
缓存加入后,有提升,可是还是差不少。
问题最终只能定位到类型的转换。Object arg0, Object[] arg1,目标对象和参数涉及到多次类型转换
在实现中,和直接调用相比,除了这一行调用其余逻辑都是额外加入的,额外的这一些逻辑就是反射的开销。
arg9999.invoke(arg10000, arg10001, arg10002);
实际使用中,为了更好的控制代理生成,可以使用javassist来生成字节码
2.1使用javassist生成代理类
生成的代理类GeneratedMethodAccessor$0.class
package sun.reflect;
import com.linghushaoxia.javabase.reflection.research.Business;
import java.lang.reflect.InvocationTargetException;
import sun.reflect.MethodAccessor;
public class GeneratedMethodAccessor$0 implements MethodAccessor {
public Object invoke(Object arg0, Object[] arg1)
throws IllegalArgumentException, InvocationTargetException {
Business arg2 = (Business) arg0;
arg2.invokePrint((String) arg1[0],
Integer.valueOf(String.valueOf(arg1[1])).intValue(),
(Long) arg1[2]);
return null;
}
}
生成方法如下
/**
*
* 功能说明:根据传入参数生成目标方法的代理对象
* @param className
* 类名
* @param methodName
* 方法名
* @param returnType
* 返回类型
* @param modifier
* 访问权限修饰符
* @param params
* 参数类型列表
* @return MethodAccessor
* @time:2017年5月1日下午11:56:57
* @author:linghushaoxia
* @exception:
*
*/
public static MethodAccessor generateMethod(String className,String methodName,Class> returnType,int modifier,Class>...params){
MethodAccessor methodResult=null;
try {
ClassPool classPool = ClassPool.getDefault();
/**
* 以下手工方式写出代理方法
* 作为示例,略去了异常处理和校验
*/
//生成的类名
String classname="sun.reflect.GeneratedMethodAccessor$"+count++;
CtClass ctClass= classPool.makeClass(classname);
classPool.importPackage("java.lang");
classPool.importPackage("java.lang.reflect");
ctClass.setModifiers(Modifier.PUBLIC);
//实现MethodAccessor,和反射api无缝结合
CtClass[] interfaces={classPool.get("sun.reflect.MethodAccessor")};
ctClass.setInterfaces(interfaces);
//方法体
StringBuilder proxyInvokeBody = new StringBuilder();
//代理方法参数的声明
StringBuilder proxyParamsDeclare = new StringBuilder();
//调用目标类的参数传值
StringBuilder targetParamValues = new StringBuilder();
for(int i=0;i<params.length;i++){
if (params[i].getName().equals("int")) {
targetParamValues.append("Integer.valueOf((String.valueOf(").append(" params[").append(i).append("]").append("))).intValue()");
}else {
targetParamValues.append("(").append(params[i].getName()).append(")").append(" params[").append(i).append("]");
}
proxyParamsDeclare.append(params[i].getName()).append(" param").append(i);
if (i<params.length-1) {
proxyParamsDeclare.append(",");
targetParamValues.append(",");
}
}
/**
* 以实现sun.reflect.MethodAccessor方式生成代理时,返回值是Object类型
* 此处返回null
*/
if (returnType==null) {
proxyInvokeBody.append("public Object invoke(Object obj,").append("Object[] params").append(")").append(" throws IllegalArgumentException, InvocationTargetException ").append("{");
proxyInvokeBody.append("\n ");
proxyInvokeBody.append(className).append(" target").append("=").append("(").append(className).append(")").append("obj").append(";");
proxyInvokeBody.append("\n ");
proxyInvokeBody.append(" target.").append(methodName).append("(").append(targetParamValues.toString()).append(")").append(";");
proxyInvokeBody.append("\n return null;");
proxyInvokeBody.append("\n }");
}else if (returnType==Void.class) {
proxyInvokeBody.append("public void invoke(Object obj,").append("Object[] params").append(")").append(" throws IllegalArgumentException, InvocationTargetException ").append("{");
proxyInvokeBody.append("\n ");
proxyInvokeBody.append(className).append(" target").append("=").append("(").append(className).append(")").append("obj").append(";");
proxyInvokeBody.append("\n ");
proxyInvokeBody.append(" target.").append(methodName).append("(").append(targetParamValues.toString()).append(")").append(";");
proxyInvokeBody.append("\n }");
}
CtMethod invokePrint =CtMethod.make(proxyInvokeBody.toString(), ctClass);
ctClass.addMethod(invokePrint);
//自定义的classloader,生成class
ReflectClassLoader classLoader = ReflectClassLoader.getClassLoader();
Class<?> generateClass= classLoader.defineClass(classname, ctClass.toBytecode());
//保存到磁盘,查看具体代码
ctClass.writeFile(classname);
methodResult= (MethodAccessor) generateClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return methodResult;
}