Java-浅谈类加载器

 前言:之前看到一道面试题,解释jvm加载.class的原理。把笔者难住了,无奈搞开发也一段时间了,连基础的东西也没搞懂,实在感到羞愧。这几天有空都在看这方面的资料,也了解了个大概。就把自己看的这些资料在脑海里整理了一下,发一个心得。不敢说全部都是正确的,因为各种原因有些东西没办法寻根问底,但是估计也八九不离十了,有错误的地方还望各位指出啊。

 

  1. 加载过程
  2. 运行程序时,首先jre会找到jvm.dll(就是传说中的java虚拟机)。至于这个怎么找到虚拟机的过程,我这里就不做讨论了。
  3. 启动jvm后,首先会生成类加载器,系统提供的类加载器有3个:
  • Bootstrap ClassLoader,这个类加载器加载基本的系统所需的类,主要加载的是(jre路径)/lib/rt.jar包。
  • Extension ClassLoader,这个类加载器加载一些扩展的类,主要加载的是:(jre路径)/lib/ext文件夹下的包。
  • AppClassLoader(又叫System ClassLoader,下面我都直接叫AppClassLoader吧),这个类主要加载的就是CLASSPATH下的包了,还有用户自定义的包,最重要的加载程序员写的程序。

4.   关于Bootstrap

 所有的.class都是由类加载器进行加载。作为类加载器的起源,Bootstrap的作用非常重要,下面就来谈一下这个Bootstrap类加载器。

Bootstrap不是由java编写的,是由C++编写的,所以在java的逻辑上面,是没有Bootstrap这个类加载器。在程序中可以通过.getClassLoader来输出一个实体对象是由哪个加载器加载,但是如果是由Bootstrap加载的类,输出的时候会是null。请看一下下面的演示代码:

public class TestPrintClassLoader {

	public static void main(String[] args) {
		A a = new A();
		/*输出自定义类A的加载器*/
		System.out.println(a.getClass().getClassLoader());
		/*cl为类加载器*/
		ClassLoader cl = a.getClass().getClassLoader();
		/*那么输出一下类加载器ClassLoader这个类又是由谁来加载*/
		System.out.println(cl.getClass().getClassLoader());
	}

}
class A{
	/**自定义类A*/
}

 

代码中A类是程序员自己编写的,按上面所说类加载器的任务,A这个类应该是由AppClassLoader来加载的,而ClassLoader.class这个应该就是由Bootstrap ClassLoader加载的。

输出结果如下:

 
 输出结果验证了这个说法。第二条输出结果为null,就是由Bootstrap ClassLoader加载的,大家可以查一下,ClassLoader.class是在java.lang包下面,也就是在rt.jar下面,启动之后是由BootstrapClassLoader加载。

有些朋友就纳闷了,输出是null而已,为什么一定就是Bootstrap ClassLoader加载?这个因为在java中所有的类都必须是类加载器加载,所以每一个类都有对应的类加载器。输出是null的话,对应就是Bootstrap了。

 

类加载器的阶层体系:

以下的内容多是看《java深度历险》的心得。

一切皆由Bootstrap开始,我们再回来讨论一下jvm加载的流程这个问题,从宏观来说,整个加载的流程是先加载好Bootstra后,再加载ExtClassLoader,然后再加载AppClassLoader,按顺序的加载必要的包和类,然后最后由AppClassLoader加载我们的程序,整个流程的效果类似于下图(该图取自《java深度历险》):



 从开始执行运行.class的代码后的整体加载流程。由Bootstrap->ExtClassLoader->AppClassLoader,这个就是类加载的阶层关系。

先来看一下下面的代码:

public class TestPrintParent {

	public static void main(String[] args) {
		ClassLoader cl = TestPrintClassLoader.class.getClassLoader();
		System.out.println(cl);
		ClassLoader cl1 = cl.getParent();
		System.out.println(cl1);
		ClassLoader cl2 = cl1.getParent();
		System.out.println(cl2);
	}
	
}

 输出的结果是:

显示的结果是AppClassLoader的Parent是ExtClassLoader,ExtClassLoader的Parent是Bootstrap。这样看起来好像是由Bootstrap加载ExtClassLoader,然后再由ExtClassLoader加载AppClassLoader,最后再由AppClassLoader加载我们自己写的类。如下图:

其实不然,AppCLassLoader并不是由ExtClassLoader加载的,都是由Bootstrap加载。我们由代码来解答吧:

public class TestPrintParent {

	public static void main(String[] args) {
		ClassLoader cl = TestPrintClassLoader.class.getClassLoader();
		System.out.println(cl);
		ClassLoader cl1 = cl.getParent();
		System.out.println(cl1);
		ClassLoader cl2 = cl1.getParent();
		System.out.println(cl2);
		System.out.println("-----------------");
		/*AppClassLoader的加载器*/
		System.out.println(cl.getClass().getClassLoader());
		/*ExtClassLoader的加载器*/
		System.out.println(cl1.getClass().getClassLoader());
	}
	
}

 以下是输出结果:

 

我们可以看到,AppClassLoader和ExtClassLoader都是由Bootstrap加载的,这就是为什么一切源于Bootstrap。

加载的过程其实是首先加载Bootstrap,Bootstrap除了做一些简单的加载初始化的工作之外,还将载入ExtClassLoader,并将Ext里面的Parent参数设定为null(说明其Parent是Bootstrap),然后还会载入

AppClassLoader,并将里面的Parent参数设定为ExtClassLoader实体(说明其Parent是ExtClassLoader)。

由此可见,类加载器由谁加载和Parent是谁没有任何关系,Parent的存在只是为了其他目的,其实就是为了双亲委派模式(下面会介绍什么是双亲委派模式)。所以这三个加载器正确的关系图应该是下面这图:

 

 


 5.  双亲委派模式:

 所谓的双清委派模式,其实就是当接到一个加载类的需求的时候,子类首先会申请让其父类来加载,以此递归,一直到达Bootstrap,如果父类没办法加载的话,才由子类去加载。

这样做有什么好处在,一方面防止重复加载,如果父类已经加载好的类,子类要求加载的时候,父类会直接返回该类的实体。另一方面没办法加载到父类已经成功加载的同名类,防止恶意篡改系统类。还有就是同一层的类加载器之间是不互通的,例如一个XXX.class类,由不同的AppClassLoader载入,它们之间是没办法通信的,这也有效地防止恶意或者误用别人的代码。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值