面试题:为什么大家都说 Java 反射慢,它到底慢在哪?

文章目录

  • 前言
  • 反射真的存在性能问题吗?
  • 反射到底慢在哪?
  • 如果避免反射导致的性能问题?
  • 总结


前言

反射具体是怎么影响性能的?这引起了我的反思。是啊,在阐述某个观点时确实有必要说明原因,并且证明这个观点是对的,虽然反射影响性能人尽皆知,我曾经也真的研究过反射是否存在性能问题,但并没有在写文章的时候详细说明。
这让我想到网上很多信息只会告诉你结论,并不会说明原因,导致很多学到的东西都是死记硬背,而不是真正掌握,别人一问或者自己亲身遇到同样的问题时,傻眼了。


反射真的存在性能问题吗?

为了放大问题,找到共性,采用逐渐扩大测试次数、每次测试多次取平均值的方式,针对同一个方法分别就直接调用该方法、反射调用该方法、直接调用该方法对应的实例、反射调用该方法对应的实例分别从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");  }  });  }  
}  

测试结果如下:
在这里插入图片描述
反射性能测试结果

测试结论:

  • 反射的确会导致性能问题;
  • 反射导致的性能问题是否严重跟使用的次数有关系,如果控制在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;  }  

沿用上面的测试方法,测试结果如下:
在这里插入图片描述

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;  
}  

沿用上面的测试方法,测试结果如下:
在这里插入图片描述

getMethod和getDeclaredField

测试结论:

  • getMethod和getDeclaredField方法会比invoke和set方法耗时;
  • 随着测试数量级越大,性能差异的比例越趋于稳定;

由于测试的这四个方法最终调用的都是native方法,无法进一步跟踪。个人猜测应该是和在程序运行时操作class有关,比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?

主要是这一系列判断条件导致了反射耗时;也有可能是因为调用natvie方法,需要使用JNI接口,导致了性能问题(参照Log.java、System.out.println,都是调用native方法,重复调用多次耗时很明显)。

如果避免反射导致的性能问题?

通过上面的测试可以看出,过多地使用反射,的确会存在性能问题,但如果使用得当,所谓反射导致性能问题也就不是问题了,关于反射对性能的影响,参照下面的使用原则,并不会有什么明显的问题:

  • 不要过于频繁地使用反射,大量地使用反射会带来性能问题;
  • 通过反射直接访问实例会比访问方法快很多,所以应该优先采用访问实例的方式。

总结

上面的测试并不全面,但在一定程度上能够反映出反射的确会导致性能问题,也能够大概知道是哪个地方导致的问题。如果后面有必要进一步测试,我会从
下面几个方面作进一步测试:

  • 测试频繁调用native方法是否会有明显的性能问题;
  • 测试同一个方法内,过多的条件判断是否会有明显的性能问题;
  • 测试类的复杂程度是否会对反射的性能有明显影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/213362.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑马点评笔记 redis实现优惠卷秒杀

文章目录 难题全局唯一IDRedis实现全局唯一Id 超卖问题问题解决方案乐观锁问题 一人一单 难题 要解决优惠卷秒杀的问题我们要考虑到三个个问题&#xff0c;全局唯一ID&#xff0c;超卖问题&#xff0c;一人一单。 全局唯一ID 用户抢购时&#xff0c;就会生成订单并保存到同一…

小程序中的大道理之二--抽象与封装

继续扒 接着 上一篇 的叙述, 健壮性也有了, 现在是时候处理点实际的东西了, 但我们依然不会一步到底, 让我们来看看. 一而再地抽象(Abstraction Again) 让我们继续无视那些空格以及星号等细节, 我们看到什么呢? 我们只看到一整行的内容, 当传入 3 时就有 3 行, 传入 4 时就…

【理解ARM架构】操作寄存器实现UART | 段的概念 | IDE背后的命令

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《理解ARM架构》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f360;操作寄存器实现UART&#x1f35f;UART原理&#x1f35f;编程 &#x1f360;…

DataFunSummit:2023年因果推断在线峰会-核心PPT资料下载

一、峰会简介 因果推断是指从数据中推断变量之间的因果关系&#xff0c;而不仅仅是相关关系。因果推断可以帮助业务增长理解数据背后的机制&#xff0c;提高决策的效率和质量&#xff0c;避免被相关性误导&#xff0c;找到真正影响业务的因素和策略。 因果推断在推荐系统中的…

电线电缆、漆包线工厂开源MES/生产管理系统/云MES

万界星空科技专业的漆包线MES系统功能介绍&#xff1a; 从原材料出入库-拉丝机等设备管理-漆包线称重打印系统自动入库&#xff08;支持多台秤同时称重&#xff09;-建立销售报价、销售订单-生产订单-支持扫码出库及自动拣货出库-应收应付账款-对接各种其他系统及财务系统。 …

一些好用的前端小插件(转自知乎)

一些好用的前端小插件&#xff08;2&#xff09; 1. cropper.js Cropper.js 2.0 是一系列用于图像裁剪的 Web 组件。 官网地址&#xff1a;https://fengyuanchen.github.io/cropperjs/v2/zh/ 2. Vditor Vditor是一款浏览器端的 Markdown 编辑器&#xff0c;支持所见即所得、…

LeetCode.283移动零(双指针)

LeetCode.283移动零 1.问题描述2.解题思路3.代码 1.问题描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1…

北京数字孪生赋能工业制造,加速推进制造业数字化转型

随着新一代信息技术与实体经济深度融合进程的加快&#xff0c;企业数字化转型需求的提升&#xff0c;政策的持续支持&#xff0c;数字孪生将为工业制造、未来生活带来无限的可能。在制造业数字化大变革时代&#xff0c;以5G、大数据、物联网、人工智能等为代表的工业4.0&#x…

Cobalt Strike的各类反向上线操作

声明&#xff1a;本文仅限于技术讨论与分享&#xff0c;严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负&#xff0c;与本号及原作者无关。 前言 Cobalt Strike 使用 GUI 框架 SWING&#xff08;一种java GUI的库&#xff09;开发&#xff0c;攻击者可通过CS木马…

2015年8月19日 Go生态洞察:Go 1.5版本发布

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【matlab程序】南海土台风画法

【matlab程序】南海土台风画法 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【python海洋专题三】图像修饰之画布和坐标轴 【Python海洋专题四】之水深地图图像修饰 【Python海洋专…

IP 代理的基础知识有哪些?

本文将介绍流冠IP代理的基础知识&#xff0c;帮助您了解IP代理的概念、类型、作用、设置方法和注意事项。 一、IP代理的概念 IP代理是一种网络代理服务&#xff0c;它通过代理服务器帮助用户访问互联网&#xff0c;并将用户的请求转发到目标网站&#xff0c;同时将目标网站的响…