类加载器
来源:互联网 发布:手机淘宝竟然被挤爆了 编辑:程序博客网 时间:2024/06/07 21:10
1 什么是类加载器
类加载器,顾名思义,就是把类加载到JVM的一个模块。一般来说JVM利用类加载器读取一个.class字节码文件并将其转换为java.lang.Class对象。然后可以通过Class的newInstance方法生成一个类的实例。基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。而实际上,ClassLoader除了能将Class加载到JVM中之外,还有一个重要的作用就是判断一个类该由谁加载,它是一个父级优先的加载机制,这就是双亲委派模型。
2 类加载器的结构
2.1 类加载器的常用方法
我们在使用类加载器的时候,经常会用到或拓展ClassLoader,下面是它的几个主要方法。
ClassLoader中与加载类相关的方法
方法
说明
getParent()
返回该类加载器的父类加载器。
loadClass(String name)
加载名称为 name
的类,返回的结果是 java.lang.Class
类的实例。
findClass(String name)
查找名称为 name
的类,返回的结果是 java.lang.Class
类的实例。
findLoadedClass(String name)
查找名称为 name
的已经被加载过的类,返回的结果是 java.lang.Class
类的实例。
defineClass(String name, byte[] b, int off, int len)
把字节数组 b
中的内容转换成 Java类,返回的结果是 java.lang.Class
类的实例。这个方法被声明为 final
的。
resolveClass(Class<?> c)
链接指定的 Java类。
defineClass方法用来将byte字节流解析成JVM能识别的Class对象,有了这个方法意味着我们不仅可以通过class文件实例化对象,还可以通过类似网络接收的方法获取字节流然后生成Class对象。这也是Java发明类加载器的原因。期初只是为了方便JavaApplet,因为需要从远程下载 Java 类文件到浏览器中并执行。现在虽然已经几乎看不到JavaApplet的身影了,但是类加载器依然有很大的使用空间。
defineClass方法通常和findClass方法一起使用,我们可以直接通过覆盖ClassLoader父类的findClass方法来实现类的加载规则,从而取得要加载的类的字节码。然后调用defineClass方法生成类的Class对象
2.2 ClassLoader的等级加载机制
等级加载机制,也就是我们经常看到的双亲委派模型。从Java虚拟机的角度来说,分类两类不同的加载器,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是由C++语言实现,它是虚拟机自身的一部分;另一种就是其他类的加载器,他们都继承自ClassLoader抽象类。下面是类加载器的结构图
- BootstrapClassloader :它的主要工作就是加载JVM自身工作需要的类,它完全由JVM控制,别人也访问不到这个类,它既没有更高一级的父加载器,也没有子加载器。所有上图中它并没有与下面的加载器连接在一起。因为对于某些虚拟机的实现来说,当一个类的parent为BootstrapClassLoader的时候,它的getParent方法返回null。它默认加载<JAVA_HOME>\lib目录中的的jar包,但只能加载被虚拟机识别的(如rt.jar等),因此即使自己写一个jar包放在这个目录下,也不会被加载的。
- ExtClassLoader:这个加载器由sun.misc.Launcher$ExtClassLoader实现,可以看出它是Launcher的一个内部类,它负责加载<JAVA_HOME>\lib\ext目录中的或者被java.ext.dirs系统变量执行的路径中的类库。
- AppClassLoader:它的父类是ExtClassLoader,它是由sun.misc.Launcher$AppClassLoader实现的,它同样是Launcher的内部类。因为它是getSystemClassLoader()方法的返回值,所以通常称为系统类加载器。它负责加载用户自定义的类,也就是说我们自己写的Java代码在没有指定类加载器的时候全部由它来加载,而我们自定义的加载器也都是它的子加载器。
下面通过一个例子来看看类加载器的结构,本例中我自定义了一个MyClassLoader类加载器,并用它实例化了一个对象。通过下面的代码来获取它的加载器“树“。
public static void getParentClassLoader(Object object){ClassLoader loader=object.getClass().getClassLoader();while(loader!=null){System.out.println(loader);loader=loader.getParent();}}输出结果如下:
test.MyClassLoader@74a14482sun.misc.Launcher$AppClassLoader@4e0e2f2asun.misc.Launcher$ExtClassLoader@3d4eac69
我们可以看到并没有输出BootstrapClassloader,这也跟我们上面的图是吻合的。在Java对getParent()方法描述中,有这样一段话,这是由于有些JVM的实现对于父类加载器是引导类加载器的情况,getParent()
方法返回null
。下面来看一下Object的类加载器情况,它的输出是不是更加可以理解上面那个getParent的描述,而且由此可以知道Object类就是由BootstrapClassloader加载的。
ClassLoader classLoader=Object.class.getClassLoader();System.out.println(classLoader);输出结果如下:
Null
3 双亲委派模型
3.1 父亲办不到啊!
在上面我们曾提到双亲委派模型,也提到getParent方法,从类加载器的结构图上来看也有了一些认识。这种层次结构就称为双亲委派模型。在双亲委派模型中,除了顶层的引导加载器,其他的加载器都有自己的父类加载器。
在这种模式下,一个加载器在收到加载请求的时候,它并不是去尝试加载这个类,而是将这个请求委派给它的父类加载器去完成,直到顶层加载器。只有当父类加载器办不到的时候,才会尝试自己去加载。
下面来看看Java源码就更好理解了:
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);//首先利用parent去加载 } 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; } }
3.2 类的唯一性
在Java中,对于任意一个类,都需要由加载器它的类加载器和这个类本身一同确立其在Java中的唯一性,每一个类加载器都有一个独立的类命名空间。也就是说想比较两个类是否相等,只有相同的类加载器加载才有意义。来看下面这个例子:
Object example=(new MyClassLoader()).findClass("test.Example").newInstance();System.out.println(example.getClass());System.out.println(example instanceof Example);
输出结果如下:
class test.Examplefalse
这里面在test包下面定义了Example类,同时自定义了一个类加载器,并用它去加载test.Example类并实例化对象。但是当利用instance of检查类型的时候却返回false。这是因为此时虚拟机中有两个Example的Class,一个是由系统默认加载的,一个是由自定义加载器加载的。虽然来自同一个Class文件,但是依然是两个独立的类。
看过上面的例子再来看看双亲委派模型的意义。使用了双亲委派模型以后,所有的类也就根据他们的类加载器定义了一个等级。如java.lang.Object类在rt.jar中,有双亲委派模型可以知道,委派到Bootstrap ClassLoader就可以加载了,因此Object类在程序的各种类加载器环境中都是一个类。如果没有这种模型,各个加载器自行去加载的话,如果我们自己定义一个java.lang.Object的类话,那系统中就会出来很多个Object类,就相当于动摇了Java的根基,也就无法生存的。
4 类加载器的几个异常
4.1 ClassNotFoundException
ClassNotFoundException恐怕是Java程序员经常碰到的异常。这个异常通常是发生在显式加载类的时候,如调用Class中的forName()方法,ClassLoader中的loadClass()方法或ClassLoader中的findSystemClass方法。这个异常主要是JVM在加载需要的字节码文件时,没有找到文件,解决办法就是检查classpath路径。获取classpath的方法:
this.getclass().getclassLoader.getResource(“”).toString();
4.2 NoClassDefFoundError
在Java文档中,对这个错误的定义如下:
Thrown ifthe Java Virtual Machine or a ClassLoader instance tries to load in thedefinition of a class (as part of a normal method call or as part of creating anew instance using the new expression) and no definition of the class could befound.
The searched-forclass definition existed when the currently executing class was compiled, butthe definition can no longer be found.
也就是在使用New关键字、属性引用某个类、继承了某个接口或类,已经方法的某个参数引用了某个类,这时会触发JVM隐式加载这些类,但发现不存在。
解决这个问题的办法就是确保每个类引用的类都在classpath下面。
5 自定义类加载器
Classloader到底有啥用?前面已经说过了,那既然有JDK提供的,为啥还要自己定义。其实大多数情况下并不需要自定义,但当我们要加载的类是经过特殊处理(如加密等)的时候,就需要自定义加载器了。同时当需要实现类的热部署的时候,自定义类加载器就不可或缺了。
下面通过重写findClass方法类实现一个类加载器
/** * 自定义类加载器 * @author songxu * */public class MyClassLoader extends ClassLoader {String dirPath=ReloadTest.class.getResource("").getPath();String srcPath=dirPath.substring(0,dirPath.indexOf("test"));static byte[]cachedata =new byte[1024];@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte [] classData=getData(name);if(diff(classData, cachedata)){cachedata=classData;System.out.println("新的数据加载");}if(null==classData){System.out.println("类名为:"+name+"无法加载");return super.loadClass(name);}else {return defineClass(null,cachedata,0,cachedata.length);}}/** * 获取字节流 * @param classname * @return */private byte [] getData(String classname){String path=srcPath+classname.replace('.', File.separatorChar)+".class";File file=new File(path);System.out.println("最后修改时间:"+getLastModify(file.lastModified()));FileInputStream inputStream=null;try {inputStream=new FileInputStream(path);byte [] buffer=new byte[inputStream.available()];inputStream.read(buffer);return buffer;} catch (Exception e) {e.printStackTrace();}finally{try {inputStream.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return null;}/** * 获取类的最后修改时间 * @param time * @return */private String getLastModify(long time){SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");Date date=new Date(time);return simpleDateFormat.format(date);}/** * 检查是否有新的类加载 * @param newdata * @param cached * @return */private boolean diff(byte []newdata,byte[]cached){if(newdata.length!=cached.length){return true;}for(int i=0;i<newdata.length;i++){if(newdata[i]!=cached[i]){return true;}}return false;}}
5.1是findClass还是loadClass?
对于自己定义类加载器,实际上还可以重写loadClass方法,从JDK源码可以看出,在这个方法中实现了双亲委派模型,而如果我们覆盖了loadClass方法,就很容易破坏到这个委派模型。重新findClass方法还是比较保险的,因为在loadClass方法中,如果双亲没有加载这个类,就会自动调用findClass方法。
- 加载、类加载、类加载器
- 类加载机制及类加载器加载Class流程
- 类加载器---类加载器简介
- Java类加载器加载类顺序
- 类加载器---类加载机制
- Java类加载器加载类顺序
- Java类加载器加载类顺序
- Java类加载器加载类顺序
- 类加载器和类加载机制
- 使用类加载器加载配置文件
- 用类加载器加载配置文件
- 用类加载器加载配置文件
- 类加载器的加载机制
- 用java类加载器加载资源
- 扩展类加载器的加载问题
- java类加载器的加载顺序
- 使用类加载器加载配置文件
- Java 类加载器以及加载机制
- MD5 和 RSA 加密算法理解
- JSON序列化与反序列化对象
- MATLAB imshow 减少空白的方法
- Mysql通过IP连接授权
- 数据结构--Chapter6(图)
- 类加载器
- php使用memcached扩展的一个BUG
- iOS自定义NavigationBar
- TFS 2010:服务器不提供Team Foundation服务,HTTP代码503,Service Unavailable
- SQL一些最基础的知识
- 欢迎使用CSDN-markdown编辑器
- Bundle类 android基础一
- swift开发中No such module 'Cocoa' 错误
- hibernate 使用jdbc批量插入数据