Java反射的底层原理,以及Java反射的性能分析及优化
java的反射技术,号称是编程界的九阳神功,也可以说是框架的灵魂。也正是这种反射机制使静态语言的java具备了动态语言的某些特质。就是有了反射,才让java动态,编程的时候更加灵活,能够动态获取信息以及动态调用对象方法。其实,Java基础技术中的代理,注解也都是依托反射才 能得以实现并应用广泛,另外我们常用的Spring、myBatis等技术框架也都是依托反射才能得以实现。
补充:动态语言 和 静态语言
(1)动态语言
动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言: Object-C.C#、JavaScript, PHP,Python, Erlang..
(2)静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java.C.C++。
总结:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活。
如何理解java的反射?反射就是把java的各种成分(字段,方法)映射成相应的java类,比如:Filed类,Method类,Constructor类等,把这些类都封装到一个java类里面,这个java类就是 java.lang.Class<?>类,如下图所示:
1. 那反射到底起了什么作用呢?反射允许程序在运行的时候(注意不是编译的时候),通过Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。例如它允许一个java类去获取它所有的成员和方法并且显示出来。这个其他程序语言所不具备的特质。
疑问:反射机制与面向对象中的封装性是不是矛盾的?如何看待这两个技术?
不矛盾。封装性和反射是两回事。封装性主要是告诉你:没必要再去调private的东西,我有public的方法,已经把功能做的很好了。反射是:我能在运行时去解刨你这个类,并且我还有能力去调用你的private的东西。所有说不矛盾,它是两种思想,反射强调的是动态性(运行时改变程序的结构),封装强调的是保密性和可重复使用性。
扩展下:封装是指隐藏对象的属性和实现细节,仅对外提供公共的访问方式。封装的原则:1. 将不需要对外提供的东西都隐藏起来;2. 把属性都隐藏,但是要提供对应的公共访问方法,你光隐藏而不提供任何访问方法也不行啊。封装的好处:1. 提高安全性;2. 提高重用性;3.方便使用。
疑问:通过直接new的方式或反射的方式都可以调用公共的结构,用哪个更好?
答:静态写代码时,直接用new的方式比较好。程序动态运行时,用反射造对象。
2. 为什么叫反射?说到反射,我们最早接触的反射就是初中物理里面光的反射,光的反射是指光在传播到不同物质时,光在两种物质分界面上改变传播方向又返回原来物质中的现象。光的反射带来的一个现象就是镜面成像。那这个跟我们Java里面的反射有什么关系呢?
要说清这个问题,我们就要从JVM的类加载以及OOP-KLASS模型说起。
反射虽然能够使java编程更加灵活,但是它的性能怎么样呢?反射调用方法和直接调用方法这两者之间在性能上面有怎样的差距呢?先说结果吧,如下图:
先解释下这个概念哈:反射调用的时候,每次获取的Method对象,其实它都是返回的一个新对象,这就会出现一个问题,比如循环获取某一Method对象,然后再执行它。这就势必导致很多的新对象产生,这个性能是最慢的。那缓存反射调用是什么意思呢,我只获取一次Method对象,然后缓存起来,最简单的方式就是将获取的Method对象提取到循环的外边,然后循环执行这一个method对象,这个就叫缓存反射调用。
看上图得出的结论:如果不做大的优化的话,如果能缓存Method对象的话,反射耗时大约是直接调用的25倍,如果不能缓存Method对象,例如代理,AOP等场景下,那么反射耗时大约是直接调用的60倍。
3. 那我们来分析下反射为什么这么慢?那我们要先说下java的编译和运行两个阶段。
编译阶段,将.java文件编译成.class文件,编译的时候都是静态的,实例类型,方法名,参数类型等都是确定的,在编译阶段编译器会做权限,可见性,参数等检验。
运行阶段,它首先是包括Java虚拟机加载类的全过程:加载,验证,准备,解析,初始化。包括这5个步骤。验证、准备、解析叫连接过程。
验证是干什么呢,就是验证文件的格式是否正确。
准备是干什么呢,准备就是给类的静态变量赋默认初始化值,注意啊,1. 静态变量,2. 赋值赋的是什么啊,是默认初始化值,它跟变量的真实值没有关系,它只跟变量的类型有关系,比如int类型,那么就赋值0,如果是Integer类型就赋值为null,boolean就赋值为false,它只跟类型有关。想一下:public static int i = 5; 那这个5是什么时候才赋值给 i 的呢? 准备阶段是默认初始化赋值的是0,那5呢,别急,后面还有个初始化阶段,这个5就是初始化阶段给赋值给 i 的。初始化就是给静态变量执行初始化,进行初始化赋值的,还有执行静态代码块,给类进行初始化。
解析是干什么呢?解析的过程会比较复杂,比如:我们的静态main方法加载进内存之后,是不是有一个对应的地址啊,解析就会将main方法这个符号标识替换成对应的内存地址的指针。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在class文件中以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现,那解析阶段中,所说的直接引用和符号引用有什么关联呢?
符号引用:符合引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,因为引用的目标不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是他们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在java虚拟机规范的class文件格式中。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
(个人理解:解析就是将符号引用变为直接引用。为什么要这么做呢?就是要将对象加载到内存中。“直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄” 这句话意味着对象已经被加载到内存中了。因为句柄的定义就是:当前对象的唯一一个标识。这里说解析阶段的目的是什么。)
回到问题的本质,Java反射性能为什么差?
性能差是相对的,是相对于我们直接调用。
直接调用的时候,是静态的 ,实例类型,方法名,参数类型这些都是明确的,编译阶段已经处理了权限,方法可见性,参数类型等校验,之后jvm加载解析的时候已经将方法的符号引用转为地址引用了,到我们执行方法的时候,就可以直接新建栈帧进行方法调用了。
那么反射的时候就不一样了,反射是动态的,在运行的过程中才知道我们要调用什么类的什么方法,在执行的时候才明确下来,但是你那些编译阶段的校验以及一些安全机制的操作 仍然 是不能少的,所以你在执行的时候依然要做校验等安全机制的操作,所以反射性能慢。另外,它是动态的,所以可能会存在一些jvm无法优化的因素。
分析完java反射的过程以及概述了反射性能慢的原因,下面进行下原因总结:
3.1. 获取Method对象慢:a. 需要检查方法权限; b. 需要遍历筛选寻找方法,甚至还要遍历父类的方法或者接口; c, 每一个Method都有一个root,不暴露给外部,而是每次copy一Method。
3.2 调用invoke方法慢: a. invoke调用方法需要对参数做封装和解封装等操作(啥意思?invoke参数是Object,那我传int, long等基本类型的参数,它里面是不是要做封装和拆封的操作?会不会产生大量的对象?);b. 调用的时候还要检查方法的权限,还要校验参数;c, invoke调用逻辑是委托给MethodAccessor的,而这个MethodAccessor对象实懒加载,你第一次调用invoke的时候才创建。
3.3. 因为是动态加载的,vm无法做优化。
补充下:
我们获取Method这个对象,其实它返回方法的拷贝getReflectionFactory().copyMethod()方法。
里面是这样的:new 一个 Method 实例并返回。
这里有两点要注意:a. 设置 root = this; b. 会给 Method 设置 MethodAccessor,用于后面方法调用。也就是所有的 Method 的拷贝都会使用同一份 methodAccessor。
介绍下啊,不展示代码了:一共有三种 MethodAccessor。
分别是MethodAccessorImpl,NativeMethodAccessorImpl,DelegatingMethodAccessorImpl。
MethodAccessorImpl 是通过动态生成字节码来进行方法调用的,是 Java 版本的 MethodAccessor,字节码生成比较复杂,这里不放代码了。大家感兴趣可以看这里的 generate 方法。
DelegatingMethodAccessorImpl 就是单纯的代理,真正的实现还是 NativeMethodAccessorImpl。
NativeMethodAccessorImpl 是 Native 版本的 MethodAccessor 实现。
采用哪种 MethodAccessor 根据 noInflation 进行判断,noInflation 默认值为 false,只有指定了 sun.reflect.noInflation 属性为 true,才会 采用 MethodAccessorImpl。所以默认会调用 NativeMethodAccessorImpl。
总结下如何去优化选用吧:
1.如果反射调用场景很少,则不需要太过纠结,直接反射调用就行了。如果可以的话,我们可以将Method对象缓存起来,并且设置检查方法的可见性为true:method.setAccessible(true); 首先我们得知道这个反射的代码是没有问题的。
2.如果对性能要求较高,且无法缓存Method对象的情况下,尽量选择AsmReflect来进行反射调用。如果可以缓存,则也可以考虑使用使用Java 版 MethodAccessor,与AsmReflect差异并不是太大。
ReflectAsm优化反射性能,它已经封装好了,它是一个java的字节码操控框架,它可以动态的生成class文件,它这就类似于直接调用了。
这个框架的pom依赖示例如下:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>reflectasm</artifactId>
<version>1.11.0</version>
</dependency>
Java反射的底层原理,以及Java反射的性能分析及优化相关推荐
- 【重难点】【Java基础 05】说一说你平时遇到的异常、什么情景下会用到反射、反射的底层原理
[重难点][Java基础 05]说一说你平时遇到的异常.什么情景下会用到反射.反射的底层原理 文章目录 [重难点][Java基础 05]说一说你平时遇到的异常.什么情景下会用到反射.反射的底层原理 一 ...
- 由浅入深探究mysql索引结构原理_性能分析与优化_由浅入深探究mysql索引结构原理、性能分析与优化...
由浅入深探究mysql索引结构原理.性能分析与优化 第一部分:基础知识第二部分:MYISAM和INNODB索引结构1, 简单介绍B-tree B+ tree树 2, MyisAM索引结构 3, Ann ...
- Java集合框架底层原理
Java集合框架 Java集合框架 List集合 ArrayList底层实现原理 ArrayList数组扩容技术(数组拷贝) 扩容大小 查询和删除 集合中的泛型 LinkedList Vector 线 ...
- java i 底层原理,《Java基础知识》Java Hash底层原理
前言 了解到JDK8对HashMap进行了优化,就一起了解一下JDK8的HashMap. 原理 1. 哈希表的原理 首先需要一张Hash表,Java通过数据实现:默认长度位16,并且. 第一步插入张三 ...
- Java集合—PriorityQueue底层原理
原文作者:汉尼博 原文地址:PriorityQueue的用法和底层实现原理 目录 先讲使用,再讲原理 实现原理 方法剖析 先讲使用,再讲原理 队列是遵循先进先出(First-In-First-Out) ...
- Java集合—ArrayList底层原理
原文作者:0 errors 0 warnings 原文地址:用大白话告诉你ArrayList的底层原理 目录 一.数据结构 二.线程安全性 三.继承关系 四.构造方法 五.add()方法 六.扩容机制 ...
- Java集合—HashMap底层原理
原文链接:最通俗易懂搞定HashMap的底层原理 HashMap的底层原理面试必考题.为什么面试官如此青睐这道题?HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好o ...
- 底层原理计划--Java连环问之空城计
核心 rocketmq redis 集合有哪些,有什么区别 ArrayList LinkedList Vector HashSet TreeSet HashMap LinkedHashMap Tree ...
- 关于Arrays.fill方法和Java赋值的底层原理
目录 问题 原理 参考 问题 今天在写leetcode的时候看到了这样一个例子: StringBuilder[] a = new StringBuilder[5]; Arrays.fill(a, ne ...
最新文章
- 数据结构与算法(十二):八大经典排序算法再回顾
- python模块-paramiko-修改源码(demo实例)
- ML/DL之预测分析类:利用机器学习算法进行预测分析的简介、分析、代码实现之详细攻略
- 获取的官方例程后怎么开发_开发商败诉后拒不赔偿怎么办,房地产纠纷处理方式有哪些?...
- vue组件一直注册不了_【报Bug】现在究竟支不支持Vue.use内注册组件
- oracle 每日归档量,小知识:统计Oracle的日归档量
- C++多线程快速入门(五)简单线程池设计
- uwp 获取listviewitem里的控件_[UWP]占领标题栏
- 循环体中对集合进行增删时报错:java.util.ConcurrentModificationException
- SQLite判断表是否存在
- 文字处理(WORD/WP)中,布局与绘制必然分开
- ConstraintLayout 完全解析 快来优化你的布局吧
- 3次样条曲线差值函数c++实现
- Boost常用库介绍
- HTML——表白(效果+代码)
- 记录下一个带内购的iOS app的上架App Store历程
- 火狐浏览器设置关闭提醒
- 音视频的同步原理——老文章,比较清晰
- asp.net摄影网站系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
- Android 实现推送功能
热门文章
- 杭州都有哪些靠谱点的互联网公司?
- 从中国质造到淘宝心选:CBM赋能“数造”新品牌
- rstudio找不到r低版本_R学习笔记-安装R和RStudio,注意RStudio的版本需要与操作系统版本匹配...
- NAND Flash SLC、MLC技术解析
- rest php,prest
- 字节流读写文件案例——模拟文件(头像)上传功能
- 虚拟机2012搭建DNS服务器,Windows Server2012 安装配置DNS服务器方法详解
- 使用Console.log调试
- 运动目标检测--三种方法比较
- solar2 android,Solar2(太阳系行星2)