前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我的反射测试结果居然与别人不一样

我的反射测试结果居然与别人不一样

作者头像
Coder昊白
发布2023-11-22 09:59:13
1700
发布2023-11-22 09:59:13
举报
文章被收录于专栏:Android菜鸟成长记录

前言

之前和群友吹水突然聊到反射,说起第一反应是耗时,但为啥耗时,大脑空空说不上来,为了防止下次面试有人问赶紧测试记录一下,没想到测试结果出人意料。

什么是反射?

反射是一种编程技术,它允许在运行时获取和操作一个程序的元数据(例如类、字段、方法、构造函数等),以及在运行时动态地创建对象、调用方法和访问成员。

反射是Java独有的特性吗?

除了Java,许多编程语言也支持类似的反射或元编程特性,允许在运行时获取和操作程序的元数据。以下是一些支持反射或类似特性的编程语言:

  1. Python:Python是一种动态语言,它具有强大的反射和元编程功能。通过使用内置的getattrsetattrhasattr等函数,开发人员可以在运行时操作对象的属性和方法。
  2. C#:C#是.NET框架的一部分,它也支持反射。通过使用System.Reflection命名空间,开发人员可以获取和操作程序集、类型、成员等信息。
  3. Ruby:Ruby是一种动态语言,具有开放的类结构,允许开发人员在运行时修改类和对象的行为。它提供了Object#sendObject#define_method等方法来实现反射和元编程。
  4. JavaScript:虽然JavaScript是一种解释性语言,但它也具有一些反射特性。开发人员可以通过Object对象的方法来获取和修改对象的属性和方法。
  5. PHP:PHP是一种常用于Web开发的脚本语言,它提供了Reflection扩展来支持反射功能,可以在运行时检查和操作类、方法、属性等信息。
  6. Kotlin:Kotlin是一种在Java虚拟机上运行的现代编程语言,它也支持类似于Java的反射功能。通过使用KClassKFunction等类型,开发人员可以在运行时获取和调用类的信息。

反射的前提条件

使用反射的前提是目标编程语言必须支持反射机制。反射是一种高级特性,它允许在运行时动态地获取、检查和操作程序的元数据,如类、方法、字段等信息。在使用反射时,需要满足以下前提条件:

  1. 编程语言支持反射: 首先,目标编程语言必须具有反射机制或提供相应的库和API,以便在运行时操作程序的结构和元数据。
  2. 目标元素的可访问性: 反射允许访问程序的私有成员和方法,但需要注意的是,访问私有成员可能违背了封装原则。在使用反射操作私有成员时,需要注意代码的安全性和设计。
  3. 运行时信息: 反射需要在运行时访问和操作元数据,因此需要有一个正在运行的程序实例。如果是静态上下文(如在程序未运行时),则无法使用反射。
  4. 性能和开销: 反射是一种强大的功能,但在使用时需要注意性能问题。反射操作通常比直接调用更消耗资源,可能会影响程序的性能。因此,在使用反射时应权衡性能和灵活性。
  5. 对编程语言的了解: 使用反射需要对编程语言的语法、类型系统和元数据有一定的了解。开发人员需要熟悉如何使用反射库或API来获取所需的信息。

反射耗时在哪里

  • 反射需要获取类的所有方法,得到一个Method数组,包含着每个方法的参数,返回值类型,权限等信息;
  • 需要遍历Method数组,得到我们需要调用的那个方法,返回其拷贝,接下来我们调用其他拷贝;
  • 通过invoke来调用拷贝的方法,在调用之前,我们要检查是否有权限执行该方法;
  • 调用方法需要对参数进行解封,因为invoke的参数类型是Object,需要将其解封为实际的参数类型;
  • 反射需要动态加载,因而无法对其进行及时**(JIT)**优化; 反射的效率损失主要集中在以上几个方面;

测试反射耗时实战

写一个简单的反射案例和正常调用分别跑1000次看他们的区别

代码语言:javascript
复制
public static void normalExecution(int count) {
long startTime = System.currentTimeMillis();

for (int i = 0; i < count; i++) {
    MyClass instance = new MyClass();
    instance.setValue(i);
    instance.doSomething();
}

long endTime = System.currentTimeMillis();
System.out.println("正常执行循环" + count + "次耗时:" + (endTime - startTime) + "毫秒");
}

public static void reflectionExecutionMethod(int count) {
long startTime = System.currentTimeMillis();

try {
    Class<MyClass> clazz = MyClass.class;
    Method doSomethingMethod = clazz.getMethod("doSomething");

    for (int i = 0; i < count; i++) {
        MyClass instance = clazz.newInstance();
        doSomethingMethod.invoke(instance);
    }
} catch (Exception e) {
    e.printStackTrace();
}

long endTime = System.currentTimeMillis();
System.out.println("使用反射执行 getMethod 循环" + count + "次耗时:" + (endTime - startTime) + "毫秒");
}

public static void reflectionExecutionDeclaredField(int count) {
long startTime = System.currentTimeMillis();

try {
    Class<MyClass> clazz = MyClass.class;
    Field valueField = clazz.getDeclaredField("value");
    valueField.setAccessible(true);

    for (int i = 0; i < count; i++) {
        MyClass instance = clazz.newInstance();
        valueField.set(instance, i);
    }
} catch (Exception e) {
    e.printStackTrace();
}

long endTime = System.currentTimeMillis();
System.out.println("使用反射 getDeclaredField 执行循环" + count + "次耗时:" + (endTime - startTime) + "毫秒");

}

static class MyClass {
    private int value;

    public void doSomething() {
    }

    public void setValue(int value) {
        this.value = value;
    }
}

这块代码我分别在编译器和Android虚拟机执行,Android虚拟机**(Pixel 4 XL API 29)** 循环1000次的结果 编译器:getDeclaredFieldgetMethod

Android虚拟机:getDeclaredFieldgetMethod

循环100000次 编译器:getDeclaredFieldgetMethod

Android虚拟机:getMethodgetDeclaredField

循环1000000次, 编译器:getMethodgetDeclaredField

Android虚拟:getDeclaredFieldgetMethod

当我第一次看到这个结果的时候也是十分不解,连续点了半个小时下来发现结果依然不同,这时我突然想到是不是编译器的JVMAndroid虚拟机的JVM不一样导致的,赶紧查了下资料。

不同的JVM

编译器的JVM(Java Virtual Machine)和Android虚拟机的JVM是两种不同的虚拟机,用于执行Java代码。它们在功能、设计和用途上有一些区别,以下是它们的比较: 编译器的JVM:

  1. 用途: 编译器的JVM是通常用于在桌面和服务器环境中运行标准的Java应用程序的虚拟机。它执行标准的Java字节码。
  2. 平台: 编译器的JVM主要用于支持标准的**Java SE(Java Standard Edition)**应用程序,可以在不同的操作系统上运行。
  3. 功能: 提供了标准的Java SE API和功能,包括图形界面、网络通信、多线程等。
  4. JIT编译: 编译器的JVM通常会使用即时编译**(JIT)**技术,在运行时将字节码编译为本机机器码,以提高执行性能。

Android虚拟机的JVM:

  1. 用途: Android虚拟机的JVM用于在Android操作系统上运行Android应用程序,它执行的是**Android DEX(Dalvik Executable)字节码,后来转变为ART(Android Runtime)**字节码。
  2. 平台: Android虚拟机的JVM是为移动设备和嵌入式系统设计的,主要用于支持Android应用程序。
  3. 功能: 提供了Android应用程序所需的功能,如UI渲染、手机硬件访问、移动网络通信等,同时也支持标准的Java核心API
  4. 运行方式: 在较早的版本中,使用Dalvik虚拟机来解释DEX字节码,而后来的版本中,转为使用ART虚拟机,通过预先编译和优化方式提高执行性能。

个人猜想的JVM性能之差

字节码解释和JIT编译: 在不同的JVM环境下,字节码的解释和JIT编译可能有所不同。在某些情况下,JIT编译器可能会对频繁调用的方法进行优化,使得getMethod在某些情况下执行更快。而getDeclaredField涉及到访问私有字段并且需要额外的访问权限,可能在某些情况下执行较慢。 优化策略: 不同的JVM可能有不同的优化策略,例如内联、方法内联等,这些优化策略会影响方法的执行性能。 类加载和初始化: 在不同的环境下,类的加载和初始化顺序可能不同,这可能会影响方法调用和字段访问的性能。 运行时环境: 不同的JVM运行在不同的硬件和操作系统上,硬件和操作系统的差异也会影响性能表现。

结论

  1. 不要在性能敏感的应用中,频繁调用反射。
  2. 如果反射执行的次数小于1000这个数量级,反射的耗时实际上与正常无异。
  3. 反射对内存占用还有一定影响的,在内存敏感的场景下,谨慎使用反射。
  4. 不同的JVM优化策略不同

后记

上面的测试并不全面,但在一定程度上能够反映出反射的确会导致性能问题,同时不同的JVM优化策略区别。如果后面有必要进一步测试,我会从下面几个方面作进一步测试:

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

参考

Java反射会影响性能吗?到底慢在哪???_java8 反射性能_sunnylovecmc的博客-CSDN博客

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是反射?
  • 反射是Java独有的特性吗?
  • 反射的前提条件
  • 反射耗时在哪里
  • 测试反射耗时实战
  • 不同的JVM
  • 个人猜想的JVM性能之差
  • 结论
  • 后记
  • 参考
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档