• 【注解和反射】性能分析


    继上一篇博客【注解和反射】通过反射动态创建对象、调用普通方法、操作属性-CSDN博客

    目录

    九、性能分析

    代码分析

    完整代码

    分析结果


    九、性能分析

    代码分析

    (1)这部分代码是一个简单的基准测试,它测量了在Java中使用普通方式调用一个对象的getter方法(即getName())10亿次所需的时间。

    1. public static void test01(){
    2. User user = new User();
    3. long starttime = System.currentTimeMillis();
    4. for (int i = 0; i < 1000000000; i++) {
    5. user.getName();
    6. }
    7. long endtime = System.currentTimeMillis();
    8. System.out.println("普通方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    9. }

    下面是代码的详细解释:

    1. User user = new User(); - 创建一个User对象实例,并赋值给变量user

    2. long starttime = System.currentTimeMillis(); - 获取当前的系统时间(以毫秒为单位),并赋值给变量starttime。这个时间将用作基准测试的起始时间。

    3. for (int i = 0; i < 1000000000; i++) { user.getName(); } - 一个循环,它将调用user.getName()方法10亿次。注意,这里只是调用了方法,但并没有使用它的返回值。这个循环的目的是为了对getName()方法进行大量的重复调用,以便能够测量其性能。

    4. long endtime = System.currentTimeMillis(); - 在循环执行完成后,再次获取当前的系统时间,并赋值给变量endtime。这个时间将用作基准测试的结束时间。

    5. System.out.println("普通方式执行10亿次,需要时间:" + (endtime - starttime) + "ms"); - 计算基准测试的执行时间(以毫秒为单位),并将其输出到控制台。这是通过从endtime中减去starttime来实现的。

    总的来说,这段代码的目的是为了测量在Java中重复调用一个对象的getter方法(即getName())的性能。但是,值得注意的是,这个测试可能并不是非常准确或有用,因为它并没有考虑JVM的热点优化等因素。在实际的性能测试中,通常需要使用更复杂的工具和方法来确保结果的准确性和可靠性。此外,getName()方法的具体实现和User类的定义也会影响测试结果。如果getName()方法非常简单,那么这个测试可能主要是测量循环和方法调用的开销,而不是getName()方法本身的性能。

    (2)这部分代码是另一个基准测试,与之前的test01方法类似,但它使用了Java的反射机制来调用User对象的getName方法。反射是Java的一个强大特性,它允许程序在运行时检查和修改类的行为。在这个测试中,反射被用来动态地调用方法,而不是像test01中那样直接调用。

    1. public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    2. User user = new User();
    3. Class c1 = user.getClass();
    4. Method getName = c1.getMethod("getName", null);
    5. long starttime = System.currentTimeMillis();
    6. for (int i = 0; i < 1000000000; i++) {
    7. getName.invoke(user, null);
    8. }
    9. long endtime = System.currentTimeMillis();
    10. System.out.println("反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    11. }

    下面是代码的详细解释:

    另外,请注意代码中可能存在的错误,特别是在使用getMethodinvoke方法时参数的处理。这些错误可能会导致NoSuchMethodExceptionIllegalArgumentException异常。

    总的来说,这段代码的目的是为了测量在Java中使用反射机制重复调用一个对象的方法的性能。反射通常比直接调用方法要慢,因为它涉及到了额外的类型检查和动态分派。这个测试可以用来量化这种性能差异。然而,需要注意的是,这个测试可能受到JVM优化、方法内联和其他因素的影响,因此在实际应用中可能需要更复杂的基准测试来获取准确的结果。

    1. User user = new User(); - 创建一个User对象实例,并赋值给变量user

    2. Class c1 = user.getClass(); - 调用user对象的getClass方法来获取其运行时类的Class对象,并将其赋值给变量c1

    3. Method getName = c1.getMethod("getName", null); - 使用Class对象的getMethod方法来获取表示getName方法的Method对象。这里传递了方法名"getName"和一个null参数,表示该方法不接受任何参数。但是,这里有一个错误:getMethod的第二个参数应该是一个Class... parameterTypes的可变参数数组,用来指定方法的参数类型。如果getName方法没有参数,应该传递一个空数组而不是null。正确的调用应该是c1.getMethod("getName")或者c1.getMethod("getName", new Class[0])

    4. long starttime = System.currentTimeMillis(); - 获取当前的系统时间(以毫秒为单位),并赋值给变量starttime。这个时间将用作基准测试的起始时间。

    5. for (int i = 0; i < 1000000000; i++) { getName.invoke(user, null); } - 一个循环,它将使用反射调用user对象的getName方法10亿次。invoke方法的第一个参数是要调用该方法的对象,第二个参数是方法的参数(如果有的话)。在这个例子中,getName方法没有参数,所以传递了null。但是,与getMethod调用类似,这里也有一个错误:如果方法没有参数,应该传递一个空数组而不是null。然而,在这种情况下,因为方法确实没有参数,所以可以直接传递null,并且它应该工作正常(尽管传递空数组可能更清晰)。

    6. long endtime = System.currentTimeMillis(); - 在循环执行完成后,再次获取当前的系统时间,并赋值给变量endtime。这个时间将用作基准测试的结束时间。

    7. System.out.println("反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms"); - 计算基准测试的执行时间(以毫秒为单位),并将其输出到控制台。

    (3)这部分代码是另一个Java基准测试,它使用反射来调用User对象的getName方法,但与之前的test02方法有所不同。这里的关键差异在于使用了getDeclaredMethod而不是getMethod,并且调用了setAccessible(true)来关闭Java的访问控制检查,从而提高反射调用的性能。

    1. public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    2. User user = new User();
    3. Class c1 = user.getClass();
    4. Method getName = c1.getDeclaredMethod("getName", null);
    5. getName.setAccessible(true);
    6. long starttime = System.currentTimeMillis();
    7. for (int i = 0; i < 1000000000; i++) {
    8. getName.invoke(user, null);
    9. }
    10. long endtime = System.currentTimeMillis();
    11. System.out.println("关闭检测的反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    12. }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    下面是代码的详细解释:

    1. User user = new User(); - 创建一个User对象实例,并赋值给变量user

    2. Class c1 = user.getClass(); - 调用user对象的getClass方法来获取其运行时类的Class对象,并将其赋值给变量c1

    3. Method getName = c1.getDeclaredMethod("getName", null); - 使用Class对象的getDeclaredMethod方法来获取表示getName方法的Method对象。这里有一个问题:getDeclaredMethod的第二个参数应该是一个Class... parameterTypes的可变参数数组,用来指定方法的参数类型。如果getName方法没有参数,应该传递一个空数组而不是null。正确的调用应该是c1.getDeclaredMethod("getName")或者c1.getDeclaredMethod("getName", new Class[0])

    4. getName.setAccessible(true); - 调用Method对象的setAccessible方法并传递true作为参数,来关闭Java的访问控制检查。这样做可以提高反射的性能,因为它避免了在每次调用方法时都进行安全检查。但是,这样做也有安全风险,因为它允许访问和修改私有方法和字段。

    5. long starttime = System.currentTimeMillis(); - 获取当前的系统时间(以毫秒为单位),并赋值给变量starttime。这个时间将用作基准测试的起始时间。

    6. for (int i = 0; i < 1000000000; i++) { getName.invoke(user, null); } - 一个循环,它将使用反射调用user对象的getName方法10亿次。invoke方法的第一个参数是要调用该方法的对象,第二个参数是方法的参数(如果有的话)。在这个例子中,getName方法没有参数,所以传递了null。但是,与getDeclaredMethod调用类似,这里也有一个错误:如果方法没有参数,应该传递一个空数组而不是null。然而,在这种情况下,因为方法确实没有参数,所以直接传递null也是可以的(尽管传递空数组可能更清晰)。

    7. long endtime = System.currentTimeMillis(); - 在循环执行完成后,再次获取当前的系统时间,并赋值给变量endtime。这个时间将用作基准测试的结束时间。

    8. System.out.println("关闭检测的反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms"); - 计算基准测试的执行时间(以毫秒为单位),并将其输出到控制台。

    总的来说,这段代码的目的是为了测量在Java中使用反射机制并且关闭访问控制检查的情况下重复调用一个对象的方法的性能。关闭访问控制检查可以提高性能,但这样做需要权衡安全性和性能之间的考虑。这个测试可以用来量化这种性能差异,并帮助开发者做出决策。

    完整代码

    1. import java.lang.reflect.InvocationTargetException;
    2. import java.lang.reflect.Method;
    3. public class Test07 {
    4. public static void test01(){
    5. User user = new User();
    6. long starttime = System.currentTimeMillis();
    7. for (int i = 0; i < 1000000000; i++) {
    8. user.getName();
    9. }
    10. long endtime = System.currentTimeMillis();
    11. System.out.println("普通方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    12. }
    13. public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    14. User user = new User();
    15. Class c1 = user.getClass();
    16. Method getName = c1.getMethod("getName", null);
    17. long starttime = System.currentTimeMillis();
    18. for (int i = 0; i < 1000000000; i++) {
    19. getName.invoke(user, null);
    20. }
    21. long endtime = System.currentTimeMillis();
    22. System.out.println("反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    23. }
    24. public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    25. User user = new User();
    26. Class c1 = user.getClass();
    27. Method getName = c1.getDeclaredMethod("getName", null);
    28. getName.setAccessible(true);
    29. long starttime = System.currentTimeMillis();
    30. for (int i = 0; i < 1000000000; i++) {
    31. getName.invoke(user, null);
    32. }
    33. long endtime = System.currentTimeMillis();
    34. System.out.println("关闭检测的反射方式执行10亿次,需要时间:" + (endtime - starttime) + "ms");
    35. }
    36. public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
    37. test01();
    38. test02();
    39. test03();
    40. }
    41. }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    从代码的执行结果可以看出,关闭安全检测比不关闭时间还长。

    分析结果

    在Java中,使用反射并关闭安全检测(通过setAccessible(true))通常是为了提高性能,因为它避免了在每次访问字段或调用方法时都进行运行时权限检查。然而,实际上,关闭安全检测并不一定总是导致性能提升,有时甚至可能导致性能下降。这种情况可能由以下几个原因造成:

    1. JVM优化:现代JVM(Java虚拟机)具有高度优化的运行时环境。在某些情况下,JVM可能已经对频繁访问的代码路径进行了内联优化或其他类型的即时编译优化。当关闭安全检查时,这些优化可能会被破坏,因为JVM无法确保代码的安全性,从而可能无法应用某些性能优化。

    2. 测试环境的差异:性能测试的结果可能受到许多因素的影响,包括系统负载、JVM的热身时间(即JIT编译器优化代码所需的时间)、GC(垃圾收集)活动以及其他后台进程。如果在不同的测试运行中环境条件不一致,那么结果可能会有所不同。

    3. 测试方法的实现:测试代码的实现方式可能会影响结果。例如,如果测试方法中包含额外的日志记录、异常处理或其他非反射相关的开销,那么这些开销可能会掩盖或甚至超过反射调用的性能差异。

    4. 方法调用的开销:即使关闭了安全检查,反射调用本身仍然比直接调用方法要慢,因为它涉及到动态解析方法签名、查找方法实现等额外步骤。在大量迭代中,这些额外的步骤可能会累积起来,导致总体性能下降。

    5. 安全检查的实际开销:在某些情况下,安全检查的开销可能并不显著,特别是在方法调用非常频繁的情况下,JVM可能已经通过某种方式优化了这些检查。

    6. 测试误差:性能测试可能受到随机误差的影响,特别是在测量非常小的时间差时。运行多次测试并取平均值通常可以减少这种误差。

    7. 代码预热:JVM的热点代码优化需要时间来进行。如果在性能测试之前没有给JVM足够的时间来预热和优化代码,那么初始的测试结果可能会比之后的运行结果更慢。

    8. 其他JVM设置:JVM的配置和参数设置(如内存分配、垃圾收集器类型等)也可能影响性能测试的结果。

    为了获得更准确的结果,建议进行以下操作:

    • 确保在性能测试之前给JVM足够的预热时间。
    • 运行多次测试并取平均值以减少随机误差。
    • 确保测试环境中没有其他显著的性能干扰因素。
    • 仔细审查测试代码,确保除了正在测试的性能差异之外,没有其他额外的开销。
    • 考虑使用专业的性能分析工具(如JProfiler、VisualVM等)来更深入地了解性能瓶颈所在。
  • 相关阅读:
    Elasticsearch启动后自动退出
    【无标题】
    设计循环队列(c语言)
    Moto edge s pro手机 WIFI和蓝牙连接不上 解决方法分享
    异地工业设备集中运维、数据采集,一招搞定
    Tcp 协议的接口测试
    露天煤矿现场调研和交流案例分享
    03 探究Kubernetes工作机制的奥秘
    基于C语言仿真实现的粒子火焰系统
    MyBatis-plus:删除操作、逻辑删除、性能分析插件(狂)
  • 原文地址:https://blog.csdn.net/qq_45956730/article/details/138187820