第六章 深入分析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类

posted @ 2016-03-10 15:57  水底的土豆  阅读(179)  评论(0)    收藏  举报