码迷,mamicode.com
首页 > 其他好文 > 详细

JVM类加载器

时间:2015-01-01 12:34:11      阅读:125      评论:0      收藏:0      [点我收藏+]

标签:虚拟机   classloader   线程   jvm   

在写这篇文章之前特意读了下十多年前的一本书的某些章节 《深入java虚拟机》,收获还是挺大,至少知道了类加载器在安全方面起到了至关重要的作用,废话不多说,来看看类加载器是什么。

我们知道我们写的java程序最终都要编译成class文件,这是一种二进制的文件,被设计的非常紧凑,因为这有利于class文件在网络中的传输,奠定java语言在分布式领域的优势,另一个优势是跨平台,也就是所谓的一次编译到处运行。当jvm执行class文件的时候,首先要做的肯定是去加载它,类加载器主要做的事情就是去加载class文件。JVM中的类加载机制是双亲委派,不知道为啥叫双亲,好奇怪的名字,姑且也这样叫吧,那么什么叫双亲委派呢?再解释这个概念的时候,先来看下JVM中默认提供的三种类加载器。

启动类加载器,bootstrap这个加载器由C++提供,java程序无法获取,此加载器也是加载器的祖宗,它没有父亲。确切的讲它加载的是jre/lib下面的jdk核心类库,也可以由-Xbootclasspath启动参数指定,值得一提的是该加载器只加载特定名字的jar,比如rt.jar,非法的jar它不去加载

扩展类加载器(Extension ClassLoader),扩展类加载器由java语言本身实现,负责加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,我们是可以直接使用该加载器的,它是bootstrap加载器的孩子。

应用程序类加载器(Application ClassLoader),也叫系统类加载器,一开始学习java的时候总会提到classpath的概念,没错,这个加载器就是加载我们指定的classpath下的类。它是扩展类加载器的孩子。同样是由java程序编写,我们也可以扩展它。

介绍完默认的三种类加载器,双亲委派的模型就很容易理解了,在jdk的最初版本的时候,类加载还不是这个模型,在jdk1.2的时候才正式有了这个概念。也就是当我们的系统类加载器试图通过loadClass去加载一个类的时候,会先把加载的动作传递给父加载器(如果有),就这样一层层的传递,如果最终的根加载器没有加载到该类,则依次返回,由子类加载,如果所有的类加载器都加载不到,则报ClassNotFoundException错误。之所以采用这样一个模型是因为考虑到安全性,假设我们自己定义了一个java.lang.Integer类的实现,并指定类加载器去加载,试想如果jvm加载的Integer类不是jdk提供的,而是我们自己写的,这将是非常危险的。但是采用委派的方式,我们自己定义的Integer类将永远不会得到加载的机会。

看下双亲委派源码:

protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	//首先检查这个类有没有被加载,这里的name是类的权限定名
	Class c = findLoadedClass(name);
	if (c == null) {
		//如果还没被加载,并且有父加载器,则委派给父加载器加载
	    try {
		if (parent != null) {
		    c = parent.loadClass(name, false);
		} else {
			//如果没有父亲,则使用bootstrap加载,可以想象,该方法最终是个native方法
		    c = findBootstrapClassOrNull(name);
		}
	    } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
			//父加载器无法加载,则自己来加载
            if (c == null) {
	        // If still not found, then invoke findClass in order
	        // to find the class.
	        c = findClass(name);
	    }
	}
	//执行解析,链接动作
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

实际上我们自己扩展的类加载器可以抛弃这种双亲委派的加载模型,而且有些时候还必须要这么做,双亲委派的一个特点是由父加载器加载的类对子加载器都可见,然而反过来确不行,而父加载器一般加载的都是底层基础的api,所以大多数情况都没有问题,但是有些特殊的情况,需要底层的api去调用用户实现的类,比如JNDI或者JDBC,JNDI的代码由启动类加载器去加载,目的就是对资源进行集中的管理和查找,各个厂商的都有自己的实现位于classpath下,JNDI要去调用其中的api,关于这种情况有个统称叫Service Provider Interface简称SPI,那么问题来了,由于父类加载器不能调用到子加载器加载的代码,而我们现在确实有这么一种诉求。为了解决这个问题,在java中有一种线程上下文加载器的概念,通过该方法可以在线程上下文设置一个加载器,Thread.currentThread().setContextClassLoader(),如果未设置会从父线程来继承classloader,如果都为设置默认为app类加载器,有了这个方法,JNDI就可以拿到这个classLoader去加载SPI代码,也就是将加载的动作由子类向上请求逆向,由父加载器委托给子类来加载,从而实现SPI代码的可见性。


JVM类加载器

标签:虚拟机   classloader   线程   jvm   

原文地址:http://blog.csdn.net/tangyongzhe/article/details/42247289

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!