类加载器 ClassLoder详解
一、类加载器定义
从作用的角度来认识类加载器:(根据API文档)
1.类加载器是一个负责加载类的对象,而ClassLoader是一个抽象类。类加载器将xxx.class文件加载到JVM,生成xxx的Class对象。(Class详见反射 点击这里)
2.类加载器被安全管理器用来表示安全范围。
二、类加载器种类及工作原理
主要有三 :
1.Bootstrap Loader(引导加载器):负责加载%JAVA_HOME%/jre/lib下的核心jar
2.Extended Loader(扩展加载器):负责加载%JAVA_HOME%/jre/lib/ext下的扩展jar
3.AppClass Loader(应用加载器):负责加载该应用程序CLASSPATH下的jar和.class文件
工作原理:
每一个类加载器都有一个与之关联的上级类加载器,类加载器加载器类时使用委托机制。当需要加载一个类时,它首先将该任务委托给上级加载器,如果上级加载器没能加载到,然后再自行加载,这是一个递归的过程。应用加载器的上级是扩展加载器,扩展加载器的上级是引导加载器,而引导加载无上级加载器。
三、类加载器的安全管理
JVM为每个类加载器维护了一个名字空间,每个名字空间只能加载一个同名的类,不同名字空间可加载同名的类。在JVM中,同一个名字空间下的类可以直接交互,不同则不可以。如果a加载器和b加载器都加载了A类,那么a加载器下的类要访问A类,那么一定是访问的a加载器下的A类。这样,通过类加载器就可以进行安全管理。
举个例子:我们自己写了一个类叫java.lang.String和类库里的String的全限定名一模一样。现在在代码中出现了String类,需要加载,过程如下。
>首先这个任务是应用加载器的,根据委托机制,应用加载器将任务委托给上级扩展加载器。
>扩展加载器接收到该任务,将任务委托给上级引导加载器。
>引导加载器加载在核心类库中寻找java.lang.String,找到了,加载,生成Class对象。
>引导加载器将结果返回给扩展加载器。
>扩展加载器将结果返回给应用加载器。
>应用加载器得到结果。
应用加载器的到的结果是引导加载器从核心类库中加载的java.lang.String 并没有加载到我们自己写的String。这样就保证了安全性。java其他很多类都依赖String 类,如果加载了我们自己写的String就会造成严重的问题。类加载器的委托机制就提供了很好的安全防护。
在例如:我们写了一个类叫java.lang.Hello,java核心类库中并不存在这个类,但它的所属的包结构却是java.lang,而同一个包下的类具有访问protected成员的权限,(protected详细解析 点击这里)这样就又会造成一些安全问题。分析一下类加载器加载Hello类的过程,按照上面的委托机制可以确定,引导、扩展加载器范围内并没有这个类,最后Hello是被应用加载器加载的,那么这个类是在应用加载器的名字空间下。开头已经给出,不同名字空间下的类不能直接交互,那么此处的java.lang.Hello并不能像核心类库的java.lang包下的类一样拥有protected的访问权限。类加载器的委托机制再一次提供了安全保护。
四、自定义类加载器
通过反射加载一个不存在的类
@Test public void funxx() throws ClassNotFoundException { Class.forName("xxx"); }
异常栈:
loaderClass源码:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { 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. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
分析以上信息,可以发现,ClassLoader加载类是通过loadClass()方法完成的,其工作流程如下:
1.调用findLoadedClass()查看该类是否被加载过,如果没有,返回null
2.如果findLoadedClass()返回null,那么使用委托机制进行类加载。
3.如果getParent().loadClass() != null , 表明加载成功,否则调用本类的findClass()方法进行加载。
由此可见,我们自定义一个类加载器,只需要继承ClassLoader,并重写其findClass()方法即可。
下面是个人写的一个ClassLoader
package cn.edu; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class CustomizedClassLoader extends ClassLoader{ private String classpath; public CustomizedClassLoader() { } public CustomizedClassLoader(String classpath) { this.classpath = classpath; } //当loaderClass没有找到该类时,会自动调用此方法寻找类。 @Override public Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = this.getBytesData(name); //获取.class文件的字节数组 if(data == null) { throw new ClassNotFoundException("找不到类:" + name); } return this.defineClass(name, data, 0, data.length); //loaderClass继承的方法,该方法通过字节数组生成Class对象 }catch(IOException e){ throw new ClassNotFoundException("找不到类:" + name); } } //读取.class文件,返回字节数组,若读取到的内容为空,则返回null private byte[] getBytesData(String name) throws IOException{ name = name.replace(".", "//") + ".class"; //解析binary name,得到文件绝对路径 File file = new File(classpath,name); byte[] res = this.readFile(file); if(res.length == 0) { //字节数组为空,返回null return null; } return res; } //读取指定文件,返回字节数组,若找不到该文件,则IO异常 private byte[] readFile(File file) throws IOException{ FileReader reader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(reader); String str = null; String res = ""; while( (str = bufferedReader.readLine()) != null) { res += str; } return res.getBytes(); } }
五、Tomcat的类加载器
Tomcat有两类类加载器,一类是服务器类加载器,用于加载${CATALINA_HOME}/lib目录下的jar,这些是web容器所依赖的jar。还有一类是应用类加载器,加载${CONTEXT_PATH}/WEB-INF下的lib和class目录,用于加载该应用所依赖的jar和class。这两种类加载器,不适用委托机制,在应用需要加载类时,首先由应用加载器加载,加载失败再由服务器加载器加载,并且class目录优先于lib目录。并且每一个应用都有一个对应的类加载器加载。所以在给项目导入jar时应该注意不能导入与服务器类库有冲突的jar。
本文个人编写,水平有限,如有错误,恳请指出,欢迎讨论分享。