Java类加载机制

背景

项目中需要调用到一个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块。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值