【重难点】【JVM 02】反射在 JVM 层面的实现流程、Tomcat 的请求流程和 JVM 的类加载情况
文章目录
一、反射在 JVM 层面的实现
反射调用有两种方式,一种是本地实现,另一种是委派实现
这里围绕 Method.invoke 方法展开,先看 invoke() 源码:
public Object invoke(Object obj, Object... args)
throws IllegalAccessExce[tion, IllegalArgumentException, InvocationTargetException{
if(!override){
if(!Reflection.quickCheckMemberAccess(clazz, modifiers)){
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; //read volatile
if(ma == null){
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
invoke() 是由 MethodAccessor 接口实现的,这个接口有两个实现类
- DelegatingMethodAccessorImpl 委派实现
- NativeMethodAvvessorImpl 本地实现
这两种实现不是相互独立的,而是相互协作的
实例:
public class InvokeDemo{
public static void target(int i){
new Exception("#"+i).pringtStackTrace();
}
public static void main(String[] args)
throws ClassNotFountException, NoSuchMethodException, InvocationTargetException, IllegalAccussException{
Class<?> invokeDemo1 = Class.forName("com.example.demo.invoke_Demo.InvokeDemo");
Method method1 = invokeDemo1.getMethod("target",int.class);
method1.invoke(null,0);
}
}
运行之后,便可以在异常栈中查找方法调用的路线:
java.lang.Exception: #0
at com.example.demo.invoke_demo.InvokeDemo.target(InvokeDemo.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.example.demo.invoke_demo.InvokeDemo.main(InvokeDemo.java:15)
这里,我们能看到,invoke 方法是先调用委派实现,然后再将请求传到本地方法实现,最后再传到目标方法使用
二、Class.forName() 和 ClassLoader.loadClass 的比较
1.相同点
都可以用来对类进行加载
2.不同点
- Class.forName 除了将类的 .class 文件加载到 JVM 之外,还会对类进行解释,执行类中的 static 代码块
- ClassLoader.loadClass 只干一件事,就是将 .class 文件加载到 JVM,不会执行 static 代码块,只有在 newInstance 才会去执行
- Class.forName 可以通过参数控制,实现和 ClassLoader.loadClass 一样的功能
Class.forName(className) 方法,内部实际调用的是 Class.forName(className, true, classloader);
第 2 个 boolean 参数表示类是否需要初始化,不写则默认需要
一旦初始化,就会触发目标对象的 static 代码块,static 变量也会被再次初始化
ClassLoader.loadClass(className) 方法,内部实际调用的方法是 ClassLoader.loadClass(className, false);
第 2 个 boolean 参数,表示目标对象是否进行连接,false 表示不进行连接
不进行连接意味着不进行包括初始化等一系列步骤,那么 static 代码块不会被触发,static 变量也不会初始化