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 Files\Java\jdk1.8.0_181\jre\lib\endorsed\rt_debug.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar; //C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar; //C:\Program Files\Java\jre1.8.0_181\lib\endorsed\rt_debug.jar System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载器加载的路径: //C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext; //C:\Windows\Sun\Java\lib\ext System.out.println(System.getProperty("java.class.path"));//app.应用.系统 类加载器加载的路径: //H:\2019326spring\蚂蚁课堂\0005-(每特教育&每特学院&蚂蚁课堂)-3期-并发编程专题-线程池原理分析\0005- //(每特教育&每特学院&蚂蚁课堂)-3期-并发编程专题-线程池原理分析\上课代码\thread_day_day06_test\bin } //手动把class文件放在bootstarp类加载器的目录下,那么这个class文件就由根加载器加载。 }
Loader1和Loader2是2个不同的类加载器对象,虽然是同一个类加载器类。Class1和class2是同一个包名和类名的类的Class对象。Loader1和Loader2构成了2个命名空间。
同一个命名空间加载同一个包和类名的类,是失败的,因为他说已经加载了这个类。
2个不同的类加载器loader1和loader2可以加载同一个包名和类名的类,因此class1和class2虽然包名和类名一样,但是再虚拟机中是可以共存的。
同一个包名和类名的类对象,由于是不同的类加载加载,所以在不同的命名空间,是不可见的,不能使用,更不能赋值和转换。Object1不能转换成object2。
类加载器找的是class对象。
子加载器可以看到父加载器的,父加载器看不到子加载器的。
一个java类是由类的完全限定名和加载这个类的定义类加载器共同决定的。
数组不是类加载器加载的,是jvm运行时候创建的。
扩展类加载器和应用类加载器是由启动类加载器加载的,启动类加载器是由jvm加载的。
Ideal自带的反编译器反编译的。
Jdbc是一个标准,oracle厂商会根据这个标准去实现,jdbc是一个标准,原生的在jdk中,Connection和Statement接口在rt.jar里面,由bootStrap加载器加载。但是Connection和Statement的实现是由厂商实现的,那么就是将厂商提供的jar包放在应用的类路径下,所以厂商的实现就不能由bootstrap加载,因为bootstarp不会去扫描应用的类路径,只能由系统加载器appclassloader加载。
Connection接口是启动类加载器加载的,Connection的实现启动类加载器加载不了,只能由系统加载器加载。Connection接口看不到他的实现,这是双亲委托出现的问题。
在jdbc,jndi,xml解析都会出现,就是说在SPI场合都有这个问题。
SPI:Service Provider Inteferce。jdbc,jndi都是SPI。Jdk服务提供者仅仅提供一些标准和接口,具体的实现由厂商来实现的。父加载器的类看不到子加载器的,子加载器的类可以看到父加载器的。
父加载器可以使用当前线程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 } }