背景
项目中需要调用到一个apk内的class方法,因此用了反射,为了彻底搞清楚反射,则再来学习一下Java的类加载机制。
注: 不同进程间是不可以用反射互调的,可以参考:https://blog.csdn.net/Grekit_Sun/article/details/114284107
类加载的基本原理
在完成代码的编写之后,编译器会将我们的java文件编译成对应的class文件(二进制字节码文件),而类加载器的作用便是在用到这些class的时候将其加载到JVM中,生成对应的class对象。
让我们来看看类加载的过程:
**加载:**加载是类加载的第一个阶段,通过类的全限定名来找到对应的class文件,将此class文件生成一个class对象。
**链接:**链接分为3个小部分,验证、准备、解析。
-
验证:验证的目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证;
-
准备:给静态方法和静态变量赋予初值,比如static int a;给其中的a赋予初值为0,但是这里不会给final修饰的静态变量赋予初值,因为被final修饰的静态变量在编译期间就已经被赋予初值了;
-
解析:主要将常量池中的符号引用替换为直接引用的过程。
**初始化:**类加载最后阶段,若该类具有超类(被继承的类),则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化。
什么是类加载器?
执行以上类加载过程的就是类加载器。系统给我们提供的类加载器有三种:启动类加载器、扩展类加载器和系统类加载器。
启动类(Bootstrap)加载器:
它是由C++实现的本。地方法,不属于Java类范畴,不能够被直接引用,主要被用于加载java所需的核心jar包,它负责将 JRE/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar包加载到内存中,属于顶级类加载器。
扩展类(ExtClassLoader)加载器:
它负责加载JRE/lib/ext
目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。
系统类(AppClassLoader)加载器:
它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader.getSystemClassLoader().loadClass()
方法可以获取到该类加载器。
BootStrap
:引导类加载器:加载都是最基础的文件ExtClassLoader
:扩展类加载器:加载都是基础的文件(App)SystemClassLoader
:应用类加载器:三方jar包和自己编写java文件
以上这3个类是如何协调工作来完成类的加载呢?
其实,它们通过委托的方式实现的。具体而言,就是当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。
public class TestLoader {
public static void main(String[] args) {
ClassLoader classLoader = TestLoader.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}
}
输出结果为:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@6ff3c5b5
null
Process finished with exit code 0
可以看出,TestLoader类是由AppClassLoader来加载的。因为BootStrap Loader类加载器先搜索其指定目录找不到TestLoader类,其次ExtClassLoader也找不到,最后AppClassLoader在ClassPath找到了TestLoader类。
注意:Bootstrap Loader是用C++语言实现的,所有在Java语言是看不到它,输出为null.
双亲委派模式,类加载器在加载类的时候如果有父加载器,会优先将加载任务委托给父类加载器执行,若父类加载还有父类加载器,则进一步委托给上层的父类加载器,直到委托给顶层类加载器(Bootstrap ClassLoader),因为顶层类加载器已经没有父类加载器了。然后由父类加载器进行类的加载,若加载失败,则逐级向下由子加载器类进行加载。
双亲委派模式的优点:
1:双亲委派模式使得类的加载有了层级优先级,通过这种层级的优先级来保证加载过的类不会被重复加载,父类已经加载过的类,子类没有必要再去加载一次。
2:其次是为了安全,比如Bootstrap ClassLoader会加载JVM需要的核心java包,这时候网络上传来了一个名字是java.lang.Integer的类,Bootstrap ClassLoader检测到该类已经被加载过了,所以直接返回Class,而不是重新加载,便可以防止核心API库被随意篡改。可能你会想到自己在classpath路径下自定义一个java.lang.myInteger类,这并不属于java核心包中,父类加载器找不到该类,所以最后交由系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常
java.lang.SecurityException: Prohibited package name: java.lang
ClassLoader类源码解析
loadClass方法
//加载class的方法,体现了双亲委派模式
protected Class<?> loadClass(String paramString, boolean paramBoolean)
throws ClassNotFoundException {//若加载失败直接抛出无法找到类的错误
synchronized (getClassLoadingLock(paramString)) {
Class localClass = findLoadedClass(paramString);//从缓存中查找该类是否已被加载
if (localClass == null) {//若没有被加载,则开始进行加载
long l1 = System.nanoTime();
try {
if (this.parent != null)//若该类加载器的父类加载器不为空,则委托其父类进行加载
localClass = this.parent.loadClass(paramString, false);
else {//若其父类加载器为null,则说明本类加载器为扩展类加载器,父类加载器为启动类加载器,尝试使用bootstrap classloader进行类的加载
localClass = findBootstrapClassOrNull(paramString);
}
} catch (ClassNotFoundException localClassNotFoundException) {
}
if (localClass == null) {//若localClass为空,则父类加载器加载失败
long l2 = System.nanoTime();
localClass = findClass(paramString);//尝试使用自定义类加载器进行加载
PerfCounter.getParentDelegationTime().addTime(l2 - l1);
PerfCounter.getFindClassTime().addElapsedTimeFrom(l2);
PerfCounter.getFindClasses().increment();
}
}
if (paramBoolean) {//通过传入的标识来控制是否要对该类进行初始化操作
resolveClass(localClass);//调用本地方法进行实现
}
return localClass;
}
}
两种加载类方式的区别
Java中Class.forName和classloader都可以用来对类进行加载。
Class.forName(“className”)
其实这种方法调运的是:Class.forName(className, true, ClassLoader.getCallerClassLoader())方法
- 参数一:className,需要加载的类的名称。
- 参数二:true,是否对class进行初始化(需要initialize)
- 参数三:classLoader,对应的类加载器
ClassLoader.laodClass(“className”);
其实这种方法调运的是:ClassLoader.loadClass(name, false)方法
- 参数一:name,需要加载的类的名称
- 参数二:false,这个类加载以后是否需要去连接(不需要linking)
可见Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。