java 反射 慢在那里_Java 反射到底慢在哪? ﹏ヽ暗。殇╰゛Y 2022-11-06 10:54 112阅读 0赞 来源:jianshu.com/p/4e2b49fa8ba1 反射真的存在性能问题吗? 反射到底慢在哪? 如果避免反射导致的性能问题? 后记 有朋友在我前两天写的一篇文章深入浅出反射底下留言,问反射具体是怎么影响性能的?这引起了我的反思。是啊,在阐述某个观点时确实有必要说明原因,并且证明这个观点是对的,虽然反射影响性能人尽皆知,我曾经也真的研究过反射是否存在性能问题,但并没有在写文章的时候详细说明。这让我想到网上很多信息只会告诉你结论,并不会说明原因,导致很多学到的东西都是死记硬背,而不是真正掌握,别人一问或者自己亲身遇到同样的问题时,傻眼了。 反射真的存在性能问题吗? 还是使用上篇文章的demo,为了放大问题,找到共性,采用逐渐扩大测试次数、每次测试多次取平均值的方式,针对同一个方法分别就直接调用该方法、反射调用该方法、直接调用该方法对应的实例、反射调用该方法对应的实例分别从1-1000000,每隔一个数量级测试一次: 测试代码如下(Person、ICompany、ProgramMonkey这三个类已在之前的文章中贴出): public class ReflectionPerformanceActivity extends Activity\{ private TextView mExecuteResultTxtView = null; private EditText mExecuteCountEditTxt = null; private Executor mPerformanceExecutor = Executors.newSingleThreadExecutor(); private static final int AVERAGE\_COUNT = 10; @Override protected void onCreate(Bundle savedInstanceState)\{ super.onCreate(savedInstanceState); setContentView(R.layout.activity\_reflection\_performance\_layout); mExecuteResultTxtView = (TextView)findViewById(R.id.executeResultTxtId); mExecuteCountEditTxt = (EditText)findViewById(R.id.executeCountEditTxtId); \} public void onClick(View v)\{ switch(v.getId())\{ case R.id.executeBtnId:\{ execute(); \} break; default:\{ \} break; \} \} private void execute()\{ mExecuteResultTxtView.setText(""); mPerformanceExecutor.execute(new Runnable()\{ @Override public void run()\{ long costTime = 0; int executeCount = Integer.parseInt(mExecuteCountEditTxt.getText().toString()); long reflectMethodCostTime=0,normalMethodCostTime=0,reflectFieldCostTime=0,normalFieldCostTime=0; updateResultTextView(executeCount + "毫秒耗时情况测试"); for(int index = 0; index < AVERAGE\_COUNT; index++)\{ updateResultTextView("第 " + (index+1) + " 次"); costTime = getNormalCallCostTime(executeCount); reflectMethodCostTime += costTime; updateResultTextView("执行直接调用方法耗时:" + costTime + " 毫秒"); costTime = getReflectCallMethodCostTime(executeCount); normalMethodCostTime += costTime; updateResultTextView("执行反射调用方法耗时:" + costTime + " 毫秒"); costTime = getNormalFieldCostTime(executeCount); reflectFieldCostTime += costTime; updateResultTextView("执行普通调用实例耗时:" + costTime + " 毫秒"); costTime = getReflectCallFieldCostTime(executeCount); normalFieldCostTime += costTime; updateResultTextView("执行反射调用实例耗时:" + costTime + " 毫秒"); \} updateResultTextView("执行直接调用方法平均耗时:" + reflectMethodCostTime/AVERAGE\_COUNT + " 毫秒"); updateResultTextView("执行反射调用方法平均耗时:" + normalMethodCostTime/AVERAGE\_COUNT + " 毫秒"); updateResultTextView("执行普通调用实例平均耗时:" + reflectFieldCostTime/AVERAGE\_COUNT + " 毫秒"); updateResultTextView("执行反射调用实例平均耗时:" + normalFieldCostTime/AVERAGE\_COUNT + " 毫秒"); \} \}); \} private long getReflectCallMethodCostTime(int count)\{ long startTime = System.currentTimeMillis(); for(int index = 0 ; index < count; index++)\{ ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); try\{ Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class); setmLanguageMethod.setAccessible(true); setmLanguageMethod.invoke(programMonkey, "Java"); \}catch(IllegalAccessException e)\{ e.printStackTrace(); \}catch(InvocationTargetException e)\{ e.printStackTrace(); \}catch(NoSuchMethodException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} private long getReflectCallFieldCostTime(int count)\{ long startTime = System.currentTimeMillis(); for(int index = 0 ; index < count; index++)\{ ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); try\{ Field ageField = programMonkey.getClass().getDeclaredField("mLanguage"); ageField.set(programMonkey, "Java"); \}catch(NoSuchFieldException e)\{ e.printStackTrace(); \}catch(IllegalAccessException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} private long getNormalCallCostTime(int count)\{ long startTime = System.currentTimeMillis(); for(int index = 0 ; index < count; index++)\{ ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); programMonkey.setmLanguage("Java"); \} return System.currentTimeMillis()-startTime; \} private long getNormalFieldCostTime(int count)\{ long startTime = System.currentTimeMillis(); for(int index = 0 ; index < count; index++)\{ ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); programMonkey.mLanguage = "Java"; \} return System.currentTimeMillis()-startTime; \} private void updateResultTextView(final String content)\{ ReflectionPerformanceActivity.this.runOnUiThread(new Runnable()\{ @Override public void run()\{ mExecuteResultTxtView.append(content); mExecuteResultTxtView.append("\\n"); \} \}); \} \} 测试结果如下: ![7c4d2f6dcd52c5942fb82548a78839eb.png][] 反射性能测试结果 测试结论: 反射的确会导致性能问题; 反射导致的性能问题是否严重跟使用的次数有关系,如果控制在100次以内,基本上没什么差别,如果调用次数超过了100次,性能差异会很明显; 四种访问方式,直接访问实例的方式效率最高;其次是直接调用方法的方式,耗时约为直接调用实例的1.4倍;接着是通过反射访问实例的方式,耗时约为直接访问实例的3.75倍;最慢的是通过反射访问方法的方式,耗时约为直接访问实例的6.2倍;反射到底慢在哪? 跟踪源码可以发现,四个方法中都存在实例化ProgramMonkey的代码,所以可以排除是这句话导致的不同调用方式产生的性能差异;通过反射调用方法中调用了setAccessible方法,但该方法纯粹只是设置属性值,不会产生明显的性能差异;所以最有可能产生性能差异的只有getMethod和getDeclaredField、invoke和set方法了,下面分别就这两组方法进行测试,找到具体慢在哪? 首先测试invoke和set方法,修改getReflectCallMethodCostTime和getReflectCallFieldCostTime方法的代码如下: private long getReflectCallMethodCostTime(int count)\{ long startTime = System.currentTimeMillis(); ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); Method setmLanguageMethod = null; try\{ setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class); setmLanguageMethod.setAccessible(true); \}catch(NoSuchMethodException e)\{ e.printStackTrace(); \} for(int index = 0 ; index < count; index++)\{ try\{ setmLanguageMethod.invoke(programMonkey, "Java"); \}catch(IllegalAccessException e)\{ e.printStackTrace(); \}catch(InvocationTargetException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} private long getReflectCallFieldCostTime(int count)\{ long startTime = System.currentTimeMillis(); ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); Field ageField = null; try\{ ageField = programMonkey.getClass().getDeclaredField("mLanguage"); \}catch(NoSuchFieldException e)\{ e.printStackTrace(); \} for(int index = 0 ; index < count; index++)\{ try\{ ageField.set(programMonkey, "Java"); \}catch(IllegalAccessException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} 沿用上面的测试方法,测试结果如下: ![4cfd3dc5f6a7d4a4cbcc6c99fe725bec.png][] invoke和set 修改getReflectCallMethodCostTime和getReflectCallFieldCostTime方法的代码如下,对getMethod和getDeclaredField进行测试: private long getReflectCallMethodCostTime(int count)\{ long startTime = System.currentTimeMillis(); ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); for(int index = 0 ; index < count; index++)\{ try\{ Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class); \}catch(NoSuchMethodException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} private long getReflectCallFieldCostTime(int count)\{ long startTime = System.currentTimeMillis(); ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12); for(int index = 0 ; index < count; index++)\{ try\{ Field ageField = programMonkey.getClass().getDeclaredField("mLanguage"); \}catch(NoSuchFieldException e)\{ e.printStackTrace(); \} \} return System.currentTimeMillis()-startTime; \} 沿用上面的测试方法,测试结果如下: ![66790b3067edff23262f3a79e6379566.png][] getMethod和getDeclaredField 测试结论: getMethod和getDeclaredField方法会比invoke和set方法耗时; 随着测试数量级越大,性能差异的比例越趋于稳定; 由于测试的这四个方法最终调用的都是native方法,无法进一步跟踪。个人猜测应该是和在程序运行时操作class有关,比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?主要是这一系列判断条件导致了反射耗时;也有可能是因为调用natvie方法,需要使用JNI接口,导致了性能问题(参照Log.java、System.out.println,都是调用native方法,重复调用多次耗时很明显)。 如果避免反射导致的性能问题? 通过上面的测试可以看出,过多地使用反射,的确会存在性能问题,但如果使用得当,所谓反射导致性能问题也就不是问题了,关于反射对性能的影响,参照下面的使用原则,并不会有什么明显的问题: 不要过于频繁地使用反射,大量地使用反射会带来性能问题; 通过反射直接访问实例会比访问方法快很多,所以应该优先采用访问实例的方式。后记 上面的测试并不全面,但在一定程度上能够反映出反射的确会导致性能问题,也能够大概知道是哪个地方导致的问题。如果后面有必要进一步测试,我会从下面几个方面作进一步测试: 测试频繁调用native方法是否会有明显的性能问题; 测试同一个方法内,过多的条件判断是否会有明显的性能问题; 测试类的复杂程度是否会对反射的性能有明显影响。 [7c4d2f6dcd52c5942fb82548a78839eb.png]: /images/20221023/2dd158781c7542b29c3d5726e8fc3c50.png [4cfd3dc5f6a7d4a4cbcc6c99fe725bec.png]: /images/20221023/acf47d3f45ed43f2aaaaff54048ce066.png [66790b3067edff23262f3a79e6379566.png]: /images/20221023/f8f155464c894f1199b5118d3cfa44ee.png
相关 如何在Java中使用反射API 在Java中,反射API允许我们动态地获取和操作对象的属性、方法等信息。以下是一些基本的使用步骤: 1. 获取类:你可以通过类名或者类的Class对象来获取一个类。 ``` 墨蓝/ 2024年09月11日 21:39/ 0 赞/ 13 阅读
相关 如何在Java中使用反射API? 在Java中,反射API(Reflection API)是一种允许程序在运行时检查类、字段和方法的信息的机制。 以下是如何在Java中使用反射API的步骤: 1. 获取Cl 叁歲伎倆/ 2024年09月11日 11:42/ 0 赞/ 20 阅读
相关 Java反射机制:在什么场景下会使用到反射? 在Java编程中,反射机制主要用于以下几种场景: 1. **运行时动态获取类信息**:通过反射API,可以在运行时获取指定类的信息,如名称、包名、父类等。 2. **创建对 ╰半夏微凉°/ 2024年09月04日 10:30/ 0 赞/ 12 阅读
相关 Java反射性能问题,慢在哪里? 有小伙伴问反射具体是怎么影响性能的?这引起了我的反思。 是啊,在阐述某个观点时确实有必要说明原因,并且证明这个观点是对的,虽然反射影响性能人尽皆知,我曾经也真的研究过反射是否 墨蓝/ 2023年09月28日 15:58/ 0 赞/ 67 阅读
相关 面试官:为什么大家都说 Java 反射慢,它到底慢在哪? 反射具体是怎么影响性能的?这引起了我的反思。是啊,在阐述某个观点时确实有必要说明原因,并且证明这个观点是对的,虽然反射影响性能人尽皆知,我曾经也真的研究过反射是否存在性能问题, ╰+攻爆jí腚メ/ 2023年09月23日 18:16/ 0 赞/ 100 阅读
相关 java的反射机制速度很慢_java反射机制Reflection浅析 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的 墨蓝/ 2022年11月07日 14:42/ 0 赞/ 128 阅读
相关 java 反射 慢在那里_Java 反射到底慢在哪? 来源:jianshu.com/p/4e2b49fa8ba1 反射真的存在性能问题吗? 反射到底慢在哪? 如果避免反射导致的性能问题? 后记 有朋友在我前两天写的一篇文 ﹏ヽ暗。殇╰゛Y/ 2022年11月06日 10:54/ 0 赞/ 113 阅读
相关 WireGuard到底好在哪? WireGuard 做加法,还是做减法,这个问题的关键在于你目之所及的范围。 在VPN领域,WireGuard这个减法很漂亮。若要理解WireGuard的简洁之美,还得从 ╰+攻爆jí腚メ/ 2022年09月08日 13:49/ 0 赞/ 1052 阅读
相关 传统的MapReduce框架慢在那里 自:http://jerryshao.me/architecture/2013/04/15/%E4%BC%A0%E7%BB%9F%E7%9A%84MapReduce%E6%A1 ╰+哭是因爲堅強的太久メ/ 2022年07月15日 04:27/ 0 赞/ 67 阅读
还没有评论,来说两句吧...