双亲委派模型-java自定义类加载器

Posted 信行合一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双亲委派模型-java自定义类加载器相关的知识,希望对你有一定的参考价值。

JVM预定义的三种类型类加载器:
首先classloader 分三个级别,最上级: bootstrap classLoader 中间级: extension classLoader 最低级: app classLoader.

1.启动(Bootstrap)类加载器:

是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

2.标准扩展(Extension)类加载器:

是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器

3.系统(System)类加载器:

是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

类加载过程:
当需要加载某个类的时候,会看看这个类是否已经被加载了,如果没有,1.会请求app 级来加载,2.app 请求 extension 级 3.extension 请求 bootstrap级, 由最高级来负责加载(这个就是双亲委派,委托 上两级的loader来做加载,所以叫双亲委派模型),如果高级的无法加载 则会将人物返回给 下一级 以此类推 最后如果双亲都不行 就由自己来加载。


为什么要用这个机制?
1.增加安全性
比如 java.lang.String 这个类,这个是jdk提供的类, 如果我们自定义个 包名:java.lang 然后在里面创建一个String 类, 当我在用String类的时候,根据前面所说,是由bootstrap级的loader 来进行加载的,这个时候它发现其实已经加载过了jdk的String了,那么就不会去加载自定义的String了,因为程序在创建一个类型之前,首先去缓存中查找是不是已经存在这个自定义的String了,如果存在了就不会再去加载了。
2.防止重复加载: 防止内存中出现多份同样的字节码
比如 java.lang.String 这个类,在很多地方都会用到,如果每一个使用到的地方都重新加载一次的话,系统中就会出现多分 java.lang.String 的实例,就会导致实例的重复。

4.线程上下文类加载器:

除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器,这个类加载器默认使用的是应用类加载器,线程上下文加载器是打破了双亲加载模型的类加载器。之所以这么说是因为线程上下文可以通过getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器,设置类加载器是为了实现自己特殊的一些业务,比如 tomcat 的jar包存在于lib, common,shared 这样好几个不同的目录下,而系统自带的类加载器无法加载这些类,因为他们都有指定的默认加载路径(详情见上面 1,2,3,启动、标准、系统类加载器解释),而其他位置的类路径它们无法加载,因此这个时候就需要自定义加载器来实现特殊类的加载了。

System.out.println(Thread.currentThread().getContextClassLoader());

结果:

sun.misc.Launcher$AppClassLoader@14dad5dc

5.自定义类加载器

类加载器与类的唯一性:
类加载器虽然只用于实现类的加载动作,但是对于任意一个类,都需要由加载它的类加载器和这个类本身共同确立其在Java虚拟机中的唯一性。通俗的说,JVM中两个类是否“相等”,首先就必须是同一个类加载器加载的,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要类加载器不同,那么这两个类必定是不相等的。

这里的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。

以下代码说明了不同的类加载器对instanceof关键字运算的结果的影响:
文件类加载器:

package loadertest;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

public class FileClassLoader extends ClassLoader 
    private String rootDir;

    public FileClassLoader(String rootDir) 
        this.rootDir = rootDir;
    

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException 
        // 获取类的class文件字节数组
        byte[] classData = getClassData(name);
        if (classData == null) 
            throw new ClassNotFoundException();
         else 
            //直接生成class对象
            return defineClass(name, classData, 0, classData.length);
        
    

    private byte[] getClassData(String className) 
        String path = classNameToPath(className);
        try 
            InputStream ins = new FileInputStream(path);
            ByteOutputStream baos = new ByteOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            // 读取类文件的字节码
            while ((bytesNumRead = ins.read(buffer)) != -1) 
                baos.write(buffer, 0, bytesNumRead);
            
            return baos.toByteArray();

         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    

    private String classNameToPath(String className) 
        return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    

    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException 
        String rootDir="D:\\\\workspaces\\\\idea\\\\spring-learning\\\\target\\\\classes";
        //创建自定义文件类加载器
        FileClassLoader loader = new FileClassLoader(rootDir);
        FileClassLoader loader1 = new FileClassLoader(rootDir);

        try 
            //加载指定的class文件
            Class<?> object1=loader.findClass("loadertest.DemoObj");
            Class<?> object2=loader1.findClass("loadertest.DemoObj");
            System.out.println(object1);
            System.out.println(object2);
            System.out.println(object1.hashCode());
            System.out.println(object2.hashCode());
            System.out.println(object1 == object2);
            //输出结果:I am DemoObj
         catch (Exception e) 
            e.printStackTrace();
        
    


打印结果:

class loadertest.DemoObj
class loadertest.DemoObj
1229416514
2016447921
false

URL 类加载器:

package loadertest;

import java.io.File;
import java.net.*;

public class FileUrlClassLoader extends URLClassLoader 

    public FileUrlClassLoader(URL[] urls, ClassLoader parent) 
        super(urls, parent);
    

    public FileUrlClassLoader(URL[] urls) 
        super(urls);
    

    public FileUrlClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) 
        super(urls, parent, factory);
    


    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException 
        String rootDir="D:\\\\workspaces\\\\idea\\\\spring-learning\\\\target\\\\classes";
        //创建自定义文件类加载器
        File file = new File(rootDir);
        //File to URI
        URI uri=file.toURI();
        URL[] urls=uri.toURL();

        FileUrlClassLoader loader = new FileUrlClassLoader(urls);

        try 
            //加载指定的class文件
            Class<?> object1=loader.findClass("loadertest.DemoObj");
            System.out.println(object1.newInstance());

            //输出结果:I am DemoObj
         catch (Exception e) 
            e.printStackTrace();
        
    

以上是关于双亲委派模型-java自定义类加载器的主要内容,如果未能解决你的问题,请参考以下文章

双亲委派模型

双亲委派机制

Java类加载 - 双亲委派模型

浅谈双亲委派模型

9.破坏双亲委派模型

双亲委派模型