jvm2

public class TT {
    public static void main(String[] args) throws Exception {
        String[] ss = new String[3];
        //String是boot加载器加载的。
        System.out.println(ss.getClass().getClassLoader());//null,
        
        int[] tr = new int[3];
        //如果元素是原生类型是没有类加载器的。
        System.out.println(tr.getClass().getClassLoader());//null。这里的null是因为原生类型,上面的null是因为boot加载器。
        
        TT[] t = new TT[3];
        //数组对象的getClassLoader()返回值跟数组元素的类加载器是一样的
        System.out.println(t.getClass().getClassLoader());//AppClassLoader@73d16e93
        
        Integer[] gg = new Integer[3];
        //如果元素是原生类型是没有类加载器的。
        System.out.println(gg.getClass().getClassLoader());//null
    }
}

ClassLoader抽象类,用于加载类,不是加载对象。给定类的二进制名字,

 "java.lang.String"

 "javax.swing.JSpinner$DefaultEditor" 内部类名字

 "java.security.KeyStore$Builder$FileBuilder$1" 内部类FileBuilder中的第一个匿名内部类名字

 "java.net.URLClassLoader$3$1"  第三个匿名内部类中的第一个匿名内部类(内部类没有名字,就用数字表示)

也可以从网络获取。

Class类里面有private final ClassLoader classLoader;

数组类的Class对象不是类加载器创建的,是由虚拟机运行时自动创建的。只有数组的特殊的。

数组对象的getClassLoader()返回值跟数组元素的类加载器是一样的; 如果元素是原生类型是没有类加载器的。

 默认是双亲委托方式,这是为了安全起见,如果要改变这种方式,就要自己实现类加载器。

每个ClassLoader都有一个private final ClassLoader parent;就是他的父类加载器,所以加载器的父类加载器是包含关系

不同的类加载器都是加载不同环境变量指定目录下的类。

public class T {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path"));//bootStrap启动.根 类加载器加载的路径:
        //C:Program FilesJavajdk1.8.0_181jrelibendorsed
t_debug.jar;
        //C:Program FilesJavajdk1.8.0_181jrelib
esources.jar;
        //C:Program FilesJavajdk1.8.0_181jrelib
t.jar;
        //C:Program FilesJavajdk1.8.0_181jrelibjsse.jar;
        //C:Program FilesJavajdk1.8.0_181jrelibjce.jar;
        //C:Program FilesJavajdk1.8.0_181jrelibcharsets.jar;
        //C:Program FilesJavajdk1.8.0_181jrelibjfr.jar;
        //C:Program FilesJavajre1.8.0_181libendorsed
t_debug.jar
        System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器加载的路径:
        //C:Program FilesJavajdk1.8.0_181jrelibext;
        //C:WindowsSunJavalibext
        System.out.println(System.getProperty("java.class.path"));//app.应用.系统  类加载器加载的路径:
        //H:2019326spring蚂蚁课堂005-(每特教育&每特学院&蚂蚁课堂)-3期-并发编程专题-线程池原理分析005-
        //(每特教育&每特学院&蚂蚁课堂)-3期-并发编程专题-线程池原理分析上课代码	hread_day_day06_testin
    }
    //手动把class文件放在bootstarp类加载器的目录下,那么这个class文件就由根加载器加载。
}

Loader1Loader22个不同的类加载器对象,虽然是同一个类加载器类。Class1class2是同一个包和类名的类的Class对象。Loader1Loader2构成了2个命名空间。

同一个命名空间加载同一个包和类名的类,是失败的,因为他说已经加载了这个类。

2个不同的类加载器loader1loader2可以加载同一个包名和类名的类,因此class1class2虽然包名和类名一样,但是再虚拟机中是可以共存的

同一个包名和类名的类对象,由于是不同的类加载加载,所以在不同的命名空间,是不可见的,不能使用,更不能赋值和转换。Object1不能转换成object2

类加载器找的是class对象。

子加载器可以看到父加载器的,父加载器看不到子加载器的。

一个java类是由类的完全限定名和加载这个类的定义类加载器共同决定的。

数组不是类加载器加载的,是jvm运行时候创建的。

扩展类加载器和应用类加载器是由启动类加载器加载的,启动类加载器是由jvm加载的。

Ideal自带的反编译器反编译的。

Jdbc是一个标准,oracle厂商会根据这个标准去实现,jdbc是一个标准,原生的在jdkConnectionStatement接口在rt.jar里面,由bootStrap加载器加载。但是ConnectionStatement实现是由厂商实现的,那么就是将厂商提供的jar包放在应用的类路径下,所以厂商的实现就不能由bootstrap加载,因为bootstarp不会去扫描应用的类路径,只能由系统加载器appclassloader加载。

Connection接口是启动类加载器加载的,Connection的实现启动类加载器加载不了,只能由系统加载器加载。Connection接口看不到他的实现,这是双亲委托出现的问题。

jdbc,jndi,xml解析都会出现,就是说在SPI场合都有这个问题。

SPI:Service Provider Intefercejdbc,jndi都是SPIJdk服务提供者仅仅提供一些标准和接口,具体的实现由厂商来实现的。父加载器的类看不到子加载器的,子加载器的类可以看到父加载器的。

父加载器可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的类加载器加载的类。这就改变了父加载器不能使用子加载器的类的情况。就改变了双亲委托模型。

线程上下文类加载器就是当前线程的类加载器

SPI:通过给当前线程设置上下文类加载器,就可以由线程的上下文类加载器来加载接口的实现类。

框架开发和组件的开发有用到线程上下文类加载器。

 

package com.ssss;
public class T {
    /*当前类加载器:
    每个类都会使用加载自身的类加载器,去加载所引用的类。
    
    线程上下文类加载器从jdk1.2引入。
    Thread.currentThread().setContextClassLoader()
    Thread.currentThread().getContextClassLoader()分别用于设置和获取上下文类加载器。
    默认下,线程继承父线程的上下文类加载器,启动应用线程的上下文加载器是系统加载器。所以默认是AppClassLoder。
    在线程中运行的代码可以通过这个类加载器加载类和资源。
    
    jdbc提供的是接口,米有提供实现。实现是厂商提供的,mysql和oracle不同的厂商来提供。
    JDBC,JNDI,JAXP:这些spi都是利用的是线程上下文类加载器。
    
    TOMCAT为每一个应用一个类加载器,使用到了类加载器的隔离,并且违反了双亲委派原则。
    */
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getContextClassLoader());//AppClassLoader,线程上下文加载器,
        System.out.println(Thread.class.getClassLoader());//null,Thread是根加载器加载的,
    }
}
package com.ssss;

public class rrr implements Runnable {
    private Thread t;
    
    public rrr() {
        t = new Thread(this);
        t.start();
    }
    
    public void run() {
        ClassLoader cl = this.t.getContextClassLoader();//这个线程的上下文类加载器是App。
        System.out.println(cl.getClass());//AppClassLoader
        System.out.println(cl.getParent().getClass());//ExtClassLoader
    }
    
    public static void main(String[] args) {
        new rrr();
    }
}
package com.ssss;

/*
 线程上下文使用模式:获取-------使用-------还原
 
 类A的依赖类也是加载A的加载器加载。
 
 线程上下文就是为了破坏java的委托机制。默认线程上下文加载器是系统app加载器,spi接口的代码中会使用线程上下文加载器
加载spi的实现类。

高层提供统一的接口让底层去实现,高层要加载底层的实现类时候,就呀通过线程上下文加载器来帮助高层加载实现类。
本质是因为高层的加载器和底层加载器不一样,而高层加载器不能加载底层的类。

运行期间放置在了线程中,无论当前程序是在启动类加载器范围还是在扩展加载器范围内,都可以通过Thread.currentThread().getContextClassLoader()
获取应用类加载器。ThreadLocal是拿空间换时间,每个线程都有一个拷贝。

ClassLoader.getSystemClassLoader()也可以获取app加载器。
 */
public class Cat {
    public static void main(String[] args) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();//获取
        try {
            Thread.currentThread().setContextClassLoader(target);
            incokeMeath();//使用线程上下文的类加载器
        }finally {
            Thread.currentThread().setContextClassLoader(cl);//还原
        }
        System.out.println(ClassLoader.getSystemClassLoader());//AppClassLoader
    }
}

 

 

原文地址:https://www.cnblogs.com/yaowen/p/10959256.html