理解类加载机制

一般来说,我们日常的开发都是在IDE上进行的,这能让我们将更多的注意力放在业务的处理上,但是久而久之我们就忘记了其底层的实现原理。这是一把双刃剑,我们看不到底层实现,但是当有某些问题出现的时候,也只有理解了其底层原理,才能更好的解决问题。

类加载的基本原理

在完成代码的编写之后,编译器会将我们的java文件编译成对应的class文件(二进制字节码文件),而类加载器的作用便是在用到这些class的时候将其加载到JVM中,生成对应的class对象。

让我们来看看类加载的过程:

加载:加载是类加载的第一个阶段,通过类的全限定名来找到对应的class文件,将此class文件生成一个class对象。

链接:链接分为3个小部分,验证、准备、解析。

验证:验证的目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证;

准备:给静态方法和静态变量赋予初值,比如static int a;给其中的a赋予初值为0,但是这里不会给final修饰的静态变量赋予初值,因为被final修饰的静态变量在编译期间就已经被赋予初值了;

解析:主要将常量池中的符号引用替换为直接引用的过程。

初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化。

什么是类加载器?

执行以上类加载过程的就是类加载器。系统给我们提供的类加载器有三种:启动类加载器、扩展类加载器和系统类加载器。

启动类(Bootstrap)加载器:

它是由C++实现的本。地方法,不属于Java类范畴,不能够被直接引用,主要被用于加载java所需的核心jar包,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,属于顶级类加载器。

扩展类(Extension)加载器:

它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。

系统类(System)加载器:

它负责加载系统类路径java -classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

说到类加载器就不得不说到其双亲委派模式,类加载器在加载类的时候如果有父加载器,会优先将加载任务委托给父类加载器执行,若父类加载还有父类加载器,则进一步委托给上层的父类加载器,直到委托给顶层类加载器(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;
		}
	}

ClassLoader类中的loadClass方法显示出了双亲委派模式,在类进行加载之前首先在缓存中查询是否该类已经被加载过了,如果加载过了则直接返回class对象,如果没有被加载过,则首先查看本类加载器的父类加载器是否存在,如果存在则委托父类加载器进行类的加载,如果父类加载器为null,代表子类加载器是扩展类加载器,而其父类是启动类加载器,使用委托给启动类加载器进行加载。若以上的父类没有加载成功,最后使用自定义的类加载器进行加载。

findClass方法

	protected Class<?> findClass(String paramString)
			throws ClassNotFoundException {
		throw new ClassNotFoundException(paramString);
	}

 findClass方法默认抛出ClassNotFoundException,子类可以通过重写findClass方法来调用自定义类加载器。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值