十、JAVA多线程:JVM类加载器(自动类加载器、双亲委托机制、类加载器命名空间、运行时包、类的卸载等)

Jvm提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到内存之中

  1. 根加载器(Bootstrap ClassLoader)
    是最顶层的加载器,是由C++编写的,主要负责虚拟机核心类库的加载,如整个java.lang包,根加载器是获取不到引用的,因此String.class.getClassLoader()返回为空 : 由:sun.boot.class.path 属性获得。
  2. 扩展类加载器(Ext ClassLoader)
    他是根加载器的子类,由纯java实现 . 主要加载 jre\lb\ext子目录里面的类库,是java.lang.URLClassLoader的子类。扩展类加载器所在的类库由 java.ext.dirs获得。
  3. 系统类加载器(Application ClassLoader)
    负责加载classpath下的类库资源,或者我们项目开发中的第三方jar,他的父加载器是扩展类加载器,同时他也是自定义类加载器的默认父加载器, 所在的类库由 java.class.path获得。

 

自定义类加载器

直接看代码:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自定义类加载器必须是ClassLoader的直接或者间接类
 *
 *
 */
public class MyClassLoader extends ClassLoader {

    // 定义默认的Class存放路径
    private final static Path DEFAULT_CLASS_DIR = Paths.get("/A" );

    private final  Path classDir ;

    // 使用默认的路径
    public MyClassLoader(){
        super();
        this.classDir = DEFAULT_CLASS_DIR ;

    }

    // 允许传入指定的class路径
    public MyClassLoader(String classDir, ClassLoader parent){

        super(parent);
        this.classDir = Paths.get(classDir);

    }

    // 重写父类的findClass方法, 重要!!!!
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        // 读取class的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        if(null ==classBytes || classBytes.length == 0 ){
            throw new ClassNotFoundException(" Can not load the class  " + name ) ;

        }
        // 调用defineClass方法定义class
        return  this.defineClass(name,classBytes,0,classBytes.length );

    }

    // 读取class的二进制数据
    private byte[] readClassBytes(String name) throws ClassNotFoundException {

        String classPath = name.replace(".",",") ;
        Path classFullPath = classDir.resolve(Paths.get(classPath+".class")) ;
        if(!classFullPath.toFile().exists()){
            throw new ClassNotFoundException(" The class  " + name + "not found." ) ;
        }

        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            Files.copy(classFullPath,baos);

            return baos.toByteArray();

        } catch (IOException e) {
            throw new ClassNotFoundException("  load the class  " + name +"  error ." ,e) ;

        }

    }
    
    @Override
    public String toString(){
        return "My ClassLoader "    ;
    }


}
public class HelloWord {
    static{
        System.out.println("Hello World Class is Initialized .");

    }

    public String welcome(){
        return "Hello World" ;
    }


}

记得将编译后的class文件放到DEFAULT_CLASS_DIR 对应的目录下。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoaderTest {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();

        Class<?> aClass = classLoader.loadClass("com.zl.step10.HelloWord") ;

        System.out.println(aClass.getClassLoader());

        Object helloWorld = aClass.newInstance();

        System.out.println(helloWorld);

        Method welcomeMethod = aClass.getMethod("welcome") ;

        String result = (String) welcomeMethod.invoke(helloWorld);

        System.out.println("Result:"+result);

    }
}

输出结果:

Connected to the target VM, address: '127.0.0.1:64540', transport: 'socket'
My ClassLoader 
Hello World Class is Initialized .
com.zl.step10.HelloWord@2d6a9952
Result:Hello World
Disconnected from the target VM, address: '127.0.0.1:64540', transport: 'socket'

Process finished with exit code 0
 

双亲委托机制介绍

       如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
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);
                } 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;
    }
}

直接看

protected Class<?> loadClass(String name, boolean resolve)

1.首先检查当前类加载器是否已经缓存过类。

2.如果存在发现父加载器,则调用父类的加载器的loadClass(name,false)方法对其进行加载。

3.如果当前类加载器不存在父类加载器,则直接调用根类加载器对该类进行加载。

4.如果当前类的所有父类都没有成功加载class ,则调用当前类加载器的findClass方法对其进行加载。(自定义类重写的方法)

5.最后如果类成功被加载,则做一些性能数据的统计

6.由于loadClass指定了resolve为false,所以不会进行连接阶段的继续执行。

   resolveClass最终调用了一个本地方法做link,这里的link主要做了这么几步事情:

  1. 验证Class以确保类装载器格式和行为正确;
  2. 准备后续步骤所需的数据结构;
  3. 解析所引用的其他类。

破坏双亲委托机制

重写loadClass方法就好,代码如下:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 破坏双亲委托机制
 *
 *
 */
public class BrokerDelegateClassLoader extends ClassLoader {

    // 定义默认的Class存放路径
    private final static Path DEFAULT_CLASS_DIR = Paths.get("/A" );

    private final  Path classDir ;

    // 使用默认的路径
    public BrokerDelegateClassLoader(){
        super();
        this.classDir = DEFAULT_CLASS_DIR ;

    }

    // 允许传入指定的class路径
    public BrokerDelegateClassLoader(String classDir, ClassLoader parent){

        super(parent);
        this.classDir = Paths.get(classDir);

    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        // 根据类的全局经名称进行加锁, 确保每一个类在多线程的情况下只被加载一次。
        synchronized (getClassLoadingLock(name)) {
            // 到已加载类的缓存中查看类是否已经被加载,如果已经加载则直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                // 首次加载 ,如果类的全路径以java 和javax开头,直接委托给系统类加载器对其加载
                if(name.startsWith("java.") || name.startsWith("javax")){
                    try{
                     c = getSystemClassLoader().loadClass(name) ;
                    }catch (Exception e) {
                        // ignore
                    }
                }else{

                    try {
                        c = this.findClass(name) ;
                    }catch (Exception e) {
                        // ignore
                    }

                    // 如果自定义类没有完成对类的记载,委托给父加载器或者系统类加载器进行加载
                    if(c == null ){
                        try {
                            if (getParent() != null) {
                                c = getParent().loadClass(name);
                            } else {
                                c = getSystemClassLoader().loadClass(name);
                            }
                        } catch (Exception e) {
                            // ignore
                        }
                    }
                }
            }

            if(null == c){
                // 无法加载,抛出异常
               throw new ClassNotFoundException("The class : "+ name + " not found ." )  ;
            }

            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }


    // 重写父类的findClass方法, 重要!!!!
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        // 读取class的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        if(null ==classBytes || classBytes.length == 0 ){
            throw new ClassNotFoundException(" Can not load the class  " + name ) ;

        }
        // 调用defineClass方法定义class
        return  this.defineClass(name,classBytes,0,classBytes.length );

    }

    // 读取class的二进制数据
    private byte[] readClassBytes(String name) throws ClassNotFoundException {

        String classPath = name.replace(".","/") ;
        Path classFullPath = classDir.resolve(Paths.get(classPath+".class")) ;
        if(!classFullPath.toFile().exists()){
            throw new ClassNotFoundException(" The class  " + name + "not found." ) ;
        }

        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            Files.copy(classFullPath,baos);

            return baos.toByteArray();

        } catch (IOException e) {
            throw new ClassNotFoundException("  load the class  " + name +"  error ." ,e) ;

        }

    }

    @Override
    public String toString(){
        return "My ClassLoader "    ;
    }


}

类加载器的明明空间、运行时包、类的卸载等

类加载器命名空间

我们知道java中很可能出现类名相同的类,但是JVM却能正常的加载,是因为我们将相同的类名的类放在了不通的包(package)下面,这个也成为命名空间,每个类加载器都有自己的命名空间,命名空间是由该加载器以及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包名+类名)相同的两个类;在不同的命名空间中,有可能出现类的完整名字相同的两个类。

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名称是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类之间才能相互访问可见(默认访问级别)的类和成员。假设用户自定义了一个类java.lang.TestCase并由用于自定义的类加载器加载,由于java.lang.TestCase和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以java.lang.TestCase不能访问核心库java.lang包中的包可见成员。

同一个命名空间内的类是相互可见的

子类加载器的命名空间包含所有父类加载器的命名空间,因此由子类加载器加载的类能看见父类加载器加载的类,相反,由父类加载器加载的类不能看见子类加载器加载的类。如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类互不可见。

运行时包

决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同,只有属于同一运行时包的类才能互相访问包可见的类和类成员,这样的限制能避免用户自定义的类冒充核心类库的类区访问核心类库的包可见成员。

初始类加载器

类卸载

本文来源于:

《Java高并发编程详解:多线程与架构设计》 --汪文君

猜你喜欢

转载自blog.csdn.net/zhanglong_4444/article/details/86151731