最近为了学习Java底层,因而学习了ClassLoader;准备下一阶段学习JVM原理
ClassLoader是什么
- ClassLoader是Java的类加载机制
ClassLoader用于动态加载class文件到内存中
Java程序写好后,是由若干个class文件组织而成的一个完整的Java应用程序;程序运行时,会调用改程序的一个入口函数来调用系统的相关功能;这些功能被封装在不同的class文件中;程序启动时,不会一次性加载程序所需要的所有class文件,而是根据需要,通过ClassLoader来动态加载某个class文件到内存中;当class文件被载入到内存后,才能被其他class所引用;
Java提供的ClassLoader
- Java提供的ClassLoader有三个
**BootStrap ClassLoader(启动类加载器):**是Java类加载层最顶层的类加载器,由C++编写而成, 已经内嵌到JVM中了;主要用来加载JDK的核心类库,如:rt.jar、resources.jar、charsets.jar等;
eg:利用以下代码获得BootStrap ClassLoader加载的jar或class文件
1 | URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); |
1 | System.out.println(System.getProperty("sun.boot.class.path")); |
1 | file:/C:/Program%20Files/Java/jre1.8.0_121/lib/resources.jar |
**Extension ClassLoader(扩展类加载器):**负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有jar;
**App ClassLoader(扩展系统类加载器):**负责加载应用程序classpath目录下的所有jar和class文件
**CustomClassLoader:**自定义ClassLoader(继承java.lang.ClassLoader类/Extension ClassLoader/App ClassLoader);然而,不能继承Bootstrap ClassLoader,因为Bootstrap ClassLoader由底层C++编写,已迁入JVM内核,当JVM启动后,Bootstrap ClassLoader也随之启动,负责加载核心类库,并后遭Extension ClassLoader和App ClassLoader类加载器;
查看父类加载器
1 | /** |
ExtensionClassLoader的parent为null因为bootstrapClassLoader不是一个普通的Java类
ClassLoader加载类的原理
原理
使用双亲委托模型来搜索类;每个ClassLoader实例都有一个父类加载器的引用(是一种包含关系);而虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有福类加载器,但可以用作其他的任务委托给它的福类加载器,这个过程是自上而下一次检查的;
首先有最顶层的类加载器Bootstrap ClassLoader视图加载,若没有加载到;则将任务转交给Extension ClassLoader试图加载,若没有加载到;则转交给App ClassLoader进行加载,若没有加载成功,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类;若都没有加载到这个类,则跳出ClassNotFoundException异常;否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的实例对象;
为什么使用双亲委托模型
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次;考虑到安全因素,若不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非改变JDK中ClassLoader搜索类的默认算法。
JVM搜索类的时候,如何判定两个class是相同的
JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的
自定义ClassLoader
三个重要方法
-
loadClassclassloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等,当通过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类需要resolve,则调用resolveClass进行链接。
-
findClass此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
-
defineClass此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如需要生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如需要加载的class是受保护的、采用不同签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。
在自定义ClassLoader时,一般只重写findClass而不是loadClass
loadClass源码
1 | protected Class<?> loadClass(String name, boolean resolve) |
JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的判断方法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可
自定义ClassLoader
来自:http://blog.csdn.net/tonytfjing/article/details/47212291
1.定义一个Person接口
1 | public interface Person { |
2.再定一个类实现这个接口
1 | public class HighRichHandsome implements Person { |
3.编写ClassLoader
1 | import java.io.ByteArrayOutputStream; |
4.测试类
1 | package classloader; |
测试结果:
Loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
I don’t care whether you are rich or not
I don’t care whether you are rich or not
遇到的一些问题
学习ClassLoader的教程是在网上看的;于是在使用sun.misc.Launcher的时候发现编译器找不到这个类;但是sun.misc这个包是存在于rt.jar的,只是apidoc中没有,那是因为sun开头的包不属于J2SE规范,是Sun公司的内部实现;
故,使用一下方法解决:
右键项目-》属性-》java bulid path-》jre System Library-》access rules-》resolution选择accessible,下面填上** 点击确定
参考资料:http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/tonytfjing/article/details/47212291