第六章 深入分析ClassLoader工作机制
ClassLoader 类加载器,负责将Class加载到JVM中,审查每个类应该由谁加载,是父优先的等级加载机制,以及将Class字节码重新解析成JVM统一要求的对象格式
- 6.1 ClassLoader类结构分析
defineClass:将byte字节流解析成JVM能够识别的Class对象
findClass:实现类的加载规则
loadClass:获取这个类的Class对象
- 6.2 ClassLoader的等级加载机制
1、Bootstrap ClassLoader:接待室服务自身的,主要加载JVM自身工作需要的类,只是一个类的加载工具。用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下内容),是用原生代码来实现的,并不继承自java.lang.ClassLoader。加载扩展类和应用程序加载器,并指定他们的父类加载器。
2、ExtClassLoader:JVM自身的一部分,这个类加载器理解为是那些与这个大会合作单位的员工会员,这些会员既不是JVM内部,也和普通的外部会员不同,所以由这个类加载。
用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar,或者java.ext.dirs路径下的内容。)Java虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载Java类。由sun.misc,Launcher$EXTClassLoader实现
3、AppClassLoader:专门接待会员服务的,父类是ExtClassLoader,服务目标是广大普通会员,所有在System.getProperty("java.class.path")目录下的类都可以被这个类加载,这个目录就是我们经常用到的classpath。一般来说,Java应用的类都是由它来完成加载的,由sun.misc.Launcher$AppClassLoader实现

JVM加载class文件到内存的两种方式
1、隐式加载:不通过在代码里调用ClassLoader来加载需要的类,而是通过JVM自动加载需要的类到内存的方式
2、显示加载:通过ClassLoader加载
- 6.3 如何加载class文件

加载字节码到内存
设置ClassLoader的搜索路径,创建URLClassPath对象时会根据传过来的URL数组中的路径来判断是文件还是jar包,根据路径不同分别创建FileLoader或JarLoader,或使用默认的加载器,当JVM调用findClass时由这几个加载器来将class文件的字节码加载到内存中
验证与解析
字节码验证:类装入器对于类的字节码检测,确保格式、行为正确
类准备:在这个阶段准备代表每个类中定义的字段、方法和实现接口所必须的数据结构
解析:在这个阶段类装入器装入类所引用的其他所有类
初始化Class对象
类中包含的静态初始化器都被执行,末尾静态字段被初始化为默认值
常见加载类错误分析
ClassNotFoundException
package cn.javaweb.sixcharacter; public class notfoundexception { notfoundexception() { //得到的是当前类class文件的URI目录 System.out.println(this.getClass().getResource("")); } public static void main(String[] args) { notfoundexception c = new notfoundexception(); try { Class.forName("notFoundException");//加载类 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
NoClassDefFoundError
java -cp example.jar Example //错误 java -cp example.jar net.xulinboExample //正确,应当包含类的包名
UnsatisfiedLinkError
package cn.javaweb.sixcharacter; public class NolibException { public native void nativeMethod(); static { //Loads the native library specified by the libname argument System.loadLibrary("Noli"); } public static void main(String[] args) { new NolibException().nativeMethod(); } }
ClassCastException
JVM在做类型转换时按照如下规则检查
对于普通对象,对象必须是目标类的实例或者目标类的子类的实例
对于数组类型,目标类型必须是数组类型或java.lang.Object、java.lang.Cloneable、java.io.Serializable
ExceptionInInitializerError
- 6.5 常用的ClassLoader分析
Tomcat创建ClassLoader
1、StandardClassLoader的创建过程:StandardClassLoader是在Bootstrap类的initClassLoader中创建,Bootstrap调用ClassLoaderFactory的creatClassLoader()方法创建StandarClassLoader对象
2、StandardClassLoader创建成功后,将设置到Bootstrap的catalinaLoader属性作为整个Tomcat的根ClassLoader。接下来Tomcat以StandarClassLoader来加载catalina.startup.Catalina类并创建对象,最终将StandarClassLoader设置懂啊Catalina的parentClassLoader属性中。后面整个Tomcat容器的加载ClassLoader都将是StandardLoader
WebappClassLoader加载
1、首先检查在WebappClassLoader是否已经加载,如果请求的类之前就被WebappClassLoader加载过,则在其缓存容器resourceEntries
2、如果resourceEntries没有,则继续检查在JVM虚拟机中是否已经加载过,也就是调用ClassLoader的findLoadedClass方法
3、如果前两个缓存没有,先调用SystemClassLoader加载请求的类,SystemClassLoader在这里是AppClassLoader,也就是在当前的JVM的ClassPath路径下查找请求的类
4、检查请求的类是否在packageTriggers定义的包名下,如果在这个设置的包目录下,则将通过StandardClassLoader类来加载
5、如果没有找到,将由WebappClassLoader来加载,WebappClassLoader将会在这个应用的WEB-INF/classes目录下查找请求的类文件的字节码。找到后将创建一个ResourceEntry对象保存这个类的元信息,并把它保存在WebappClassLoader的resourceEntries容器中便于下次查找,接着调用defineClass方法生成请求类的Class的对象并返回给InstanceManager来创建实例
- 6.6 如何实现自己的ClassLoader
加载自定义路径下的class文件
package cn.javaweb.sixcharacter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class PathClassLoader extends ClassLoader{ private String packageName = "net.xulingbo.classloader"; private String classPath; public PathClassLoader(String classPath) { this.classPath = classPath; } protected Class<?> findClass(String name) throws ClassNotFoundException { if (packageName.startsWith(name)) { byte[] classDate = getData(name); if (classDate == null) { throw new ClassNotFoundException(); } else { //defineClass:将byte字节流解析成JVM能够识别的Class对象 return defineClass(name, classDate, 0, classDate.length); } } else { return super.loadClass(name);//父类加载器 } } private byte[] getData(String className) { try { String path = classPath + File.pathSeparatorChar + className.replace('.', File.separatorChar) + ".class"; InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } }
package cn.javaweb.sixcharacter; import java.net.URL; import java.net.URLClassLoader; public class URLPathClassLoader extends URLClassLoader{ private String packageName = "net.xulingbo.classloader"; /** * @param urls * @param parent */ public URLPathClassLoader(URL[] classPath, ClassLoader parent) { super(classPath, parent); } protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (aClass != null) { return aClass; } if (!packageName.startsWith(name)) { return super.loadClass(name);//loadClass:获取这个类的Class对象 } else { return findClass(name);//findClass:实现类的加载规则 } } }
加载自定义格式的class文件
package cn.javaweb.sixcharacter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; public class NetClassLoader extends ClassLoader{ private String classPath; private String packageName = "net.xulingbo.classloader"; public NetClassLoader(String classPath) { this.classPath = classPath; } protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (aClass != null) { return aClass; } if (packageName.startsWith(name)) { byte[] classDate = getDate(name); if (classDate == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classDate, 0, classDate.length); } } else { return super.loadClass(name); } } private byte[] getDate(String className) { String path = classPath + File.separatorChar + className.replace('.', File.separatorChar); try { URL url = new URL(path); //Opens a connection to this URL and returns an InputStream for reading from that connection. InputStream is = url.openStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num);; } return stream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] deCode(byte[] src) { byte[] decode = null; //对src字节码进行解码处理 return decode; } }
- 6.7 实现类的热部署
JVM判断一个类是否是同一个类的两个条件
1、看这个类的完整类名是否一样,包括类所在的包名
2、看加载这个类的ClassLoader是否是同一个(指ClassLoader的实例是否是同一个)
package cn.javaweb.sixcharacter; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class ClassReloader extends ClassLoader{ private String classPath; String classname = "compile.Yufa"; public ClassReloader(String classPath) { this.classPath = classPath; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classDate = getDate(name); if (classDate == null) { throw new ClassNotFoundException(); } else { return defineClass(classname, classDate, 0, classDate.length); } } private byte[] getDate(String className) { String path = classPath + className; try { InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { try { String path = "D:/devtools/compile/target/compile/"; ClassReloader reloader = new ClassReloader(path); Class r = reloader.findClass("Yufa.class"); System.out.println(r.newInstance()); ClassReloader reloader2 = new ClassReloader(path); Class r2 = reloader2.findClass("Yufa.class"); System.out.println(r2.newInstance()); } catch (Exception e) { e.printStackTrace(); } } }
6.8 java.class.ClassLoader类API详细介绍
——getParent() 返回该类加载器的父类加载器
——loadClass(String name)加载名称为name的类,返回结果是java.lang.Class类的实例。此方法负责加载指定名字的类。首先会从已加载的类中去寻找,如果没有找到,从parent classLoader[EXTClassLoader]中加载,如果没有加载到,则从Bootstrap ClassLoader中尝试加载(findBootstrapClassOrNull方法),如果还是加载失败,则自己加载。如果还不能加载,则抛出异常ClassNotFoundException。如果要改变加载顺序可以覆盖此方法。
——findClass(String name)查找名称为name的类,返回结果是java.lang.Class类的实例
——findLoaderClass(String name)查找名称为那么的已经被加载过的类,返回结果是java.lang.Class类的实例
——defineClass(String name, byte[] b, int off, int len)把字节数组b的内容转换成java类,返回结果是java.lang.Class类的实例。这个方法被声明为final的。
——resolveClass(Class<?> c)连接指定的Java类

浙公网安备 33010602011771号