JVM底层操作总结:反射、动态代理、lambda、注解等

  • 反射:https://www.cnblogs.com/yougewe/p/10125073.html

将Java文件保存到本地硬盘后,编译Java文件,生成.class文件,然后使用JVM将字节码文件加载到内存,字节码文件在内存(方法区)中生成一个Class类表示此Java对象

使用反射的时候,首先获取到Class类,这样就可以得到class文件里的所有内容,包含属性、构造方法、普通方法;
属性通过Filed类表示,构造方法通过Constructor表示,普通方法通过Method表示

Class.forName(name):先获取ClassLoader, 然后调用native方法获取Class信息,最后,jvm会回调 ClassLoader 进行类的加载

newInstance():主要就是拿到无参的构造器进行调用,而获取匹配的构造器则分为三步:
1 先获取所有的constructors, 然后通过进行参数类型比较
2 找到匹配后,通过 ReflectionFactory copy一份constructor返回,否则抛出 NoSuchMethodException

class.getDeclaredMethod(nam):获取所有方法列表,根据要调用的方法名称,去方法列表里选出匹配的方法即可,如果没有则抛异常

Method.invoke(Object obj, Object… args):通过native的invoke0()执行;
method.invoke()方法支持多态特性,其native实现在方法真正执行之前通过动态连接或者虚方法表来实现:https://blog.csdn.net/wenyuan65/article/details/81145900,总体来说就是:

当反射调用的方法是接口方法时,调用Reflection::resolve_interface_call(),该方法依赖LinkResolver::resolve_interface_call()来完成方法的动态链接过程

当反射调用的方法是可以被覆盖的方法,例如Animal.print(), Reflection::invoke()最终通过查询虚方法表vtable来确定最终的method。

(针对方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚拟方法表的数据结构(virtual method table,vtable),

针对于invokeinterface指令来说,虚拟机会建立一个叫做接口方法表的数据结构(interface method table,itable):https://blog.csdn.net/wzq6578702/article/details/82712667)

方法的反射调用会带来不少性能开销,原因主要有三个:
1 变长参数方法导致的 Object 数组;
2 基本类型的自动装箱、拆箱;
3 在生产环境中往往拥有多个不同的反射调用,对应多个 GeneratedMethodAccessor,也就是动态实现,由于 JVM关于调用点(Method.invoke)的类型 profile(注:对于invokevirtual或者invokeinterface,Java 虚拟机会记录下调用者的具体类型,称之为类型 profle)无法同时记录这么多个类,因此可能造成所测试的反射调用没有被内联的情况,也就是说无法进行方法内联
(方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用,也就是把函数调用的方法直接内嵌到方法内部,减少函数调用的次数 )

反射的优化:Object 数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱,因此可以扩大缓存的范围;每次反射调用都会检查目标方法的权限,这个检查可以在 Java 代码里关闭;可以关闭反射调用的 Infation 机制,从而取消委派实现,并且直接使用动态实现

  • JDK动态代理:https://www.cnblogs.com/zuidongfeng/p/8735241.html

为业务接口创建代理类的字节码文件,使用ClassLoader将字节码文件加载到JVM,然后获取此类的Class对象,使用其构造函数,传入InvocationHandler接口的实现类,通过反射来创建此代理类,最终返回给使用者:

return cons.newInstance(new Object[]{h});

查看此类,可以看到此代理类(命名如$Proxy0)继承自Proxy,实现了业务接口,将实现了InvocationHandler接口的类组合进Proxy,会生成业务接口的所有方法,这些方法的内部都大致如下:

super.h.invoke(this, m3, (Object[])null);//m3是Method类型的变量,为接口的方法

即调用Proxy内部的InvocationHandler接口实现类的invoke()方法,所以我们只需要在InvocationHandler的实现类中组合进业务接口的实现类,调用此实现类的方法即可:

private Object target;//target即为传进来的业务接口实现类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
   System.out.println("Do something before");
   Object result = method.invoke(target, args);
   System.out.println("Do something after");
   return result;
}
  • CGlib动态代理

create()方法里有一行代码会生成代理对象的字节码:

byte[] b = strategy.generate(this);

根据此字节码生成的代理对象(命名如xxxService$$EnhancerByCGLIB$$4da4ebaf)会继承自业务类,重写了业务类的方法,重写的方法内部都会调用MethodInterceptor接口实现类的intercept()方法,此方法可通过MethodProxy来调用父类(即业务类)的方法;

创建的此代理对象一般会被赋值给业务类,那么就是“父类引用指向子类对象”,调用方法时,会调用被代理类重写的方法,也就达到了代理的目的

  • lambda

编译器会为每一个lambda表达式生成一个方法,方法名是lambda$0,1,2,3,但方法引用的表达式不会生成方法。

在出现lambda的地方会产生一个invokeDynamic指令,这个指令会调用bootstrapMethod(),bootstrapMethod()在源码中并不存在,是由INDY产生的,调用了MethodHandlers$Lookup的findStatic()方法,产生要调用的方法的MethodHandler,然后使用这个MethodHandlers传给LambdaMetafactory.metafactory静态方法,此方法会创建一个ConstantCallSite对象,最后将这个对象返回给invokedynamic指令,,实现了调用(ConstantCallSite里面包含了MethodHandler,也就是最终调用的方法。)

  • 注解:https://www.cnblogs.com/throwable/p/9747595.html
    注解最后转化为一个接口,注解中定义的注解成员属性会转化为抽象方法;
    然后JVM会通过动态代理生成一个实现了"注解对应接口"的实例,该代理类实例实现了"注解成员属性对应的方法",这个步骤类似于"注解成员属性"的赋值过程,这样子就可以在程序运行的时候通过反射获取到注解的成员属性

  • Stream
    通过pipeline来完成,本质是一个拦截器链的设计

发布了235 篇原创文章 · 获赞 264 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41594698/article/details/104336717