赞
踩
Java类加载器是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
文末有福利~
图示:
1.1、类型的加载——这里的类型是指的什么?
答:类型就是指的我们Java源代码通过编译后的class文件。
1.2、类型的来源有哪些?
(1)本地磁盘
(2)网络下载,class文件
(3)war,jar下加载,class文件
(4)从专门的数据库中读取,class文件(少见)
(5)将java源文件动态编译成class文件
1)典型的就是动态代理,通过运行期生成class文件
2)我们的jsp会被转换成servlet,而我们的serlvet是一个java文件,会被编译成class文件
1.3、通过什么来进行加载?(类加载器)
1.4、类加载的分类以及各种加载职责以及层级结构
(1)系统级别
1)启动类加载器
2)扩展类加载器
3)系统类加载器(App类加载器)
(2)用户级别的
自定义类加载器(继承我们的ClassLoader)
(3)层级结构
在双亲委派机制中,各个加载器按照父子关系形成树型结构,除了根加载器以外,每一个加载器有且只有一个父加载器
1 protected Class> loadClass(String name, boolean resolve)2 throws ClassNotFoundException3 {4 synchronized (getClassLoadingLock(name)) {5 //检查当前的class对象是否被加载过,被加载过就返回6 Class> c = findLoadedClass(name);7 if (c == null) {8 long t0 = System.nanoTime();9 try {10 //判断当前的classLoader是否有父类11 //若存在父类12 if (parent != null) {13 //调用父类的loadClass14 c = parent.loadClass(name, false);15 } else {//不存在父类,表示当前的classLoader是extClassLoader16 //那么就会调用启动类判断是否加载过17 c = findBootstrapClassOrNull(name);18 }19 } catch (ClassNotFoundException e) {20 // ClassNotFoundException thrown if class not found21 // from the non‐null parent class loader22 }23 //到目标位置,app ext boot都没有去加载过24 if (c == null) {25 // If still not found, then invoke findClass in order26 // to find the class.27 long t1 = System.nanoTime();28 //委托我们的子类的classLoader去找29 c = findClass(name);3031 // this is the defining class loader; record the stats32 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0);33 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);34 sun.misc.PerfCounter.getFindClasses().increment();35 }36 }37 if (resolve) {38 resolveClass(c);39 }40 return c;41 }42 }
总所周知:java.lang.object类是所有类的父类,所以我们程序在运行期间会把java.lang.object类加载到内存中,假如java.lang.object类能够被我们自定义类加载器去加载的话,那么jvm中就会存在多份Object的Class对象,而且这些Class对象是不兼容的。
所以双亲委派模型可以保证java核心类库下的类型的安全。
借助双亲委派模型,我们java核心类库的类必须是由我们的启动类加载器加载的,这样可以确保我们核心类库只会在jvm中存在一份这就不会给自定义类加载器去加载我们核心类库的类。
根据我们的演示案例,一个class可以由多个类加载器去加载,同事可以在jvm内存中存在多个不同版本的Class对象,这些对象是不兼容的。
(1)我们自定义类加载器必须要继承ClassLoader
(2)我们必须要findClass(String name)方法
1 /**2 * Finds the class with the specified binary name.3 * This method should be overridden by class loader implementations that4 * follow the delegation model for loading classes, and will be invoked by5 * the {@link #loadClass loadClass} method after checking the6 * parent class loader for the requested class. The default implementation7 * throws a ClassNotFoundException.8 *9 * @param name10 * The binary name of the class11 *12 * @return The resulting Class object13 *14 * @throws ClassNotFoundException15 * If the class could not be found16 *17 * @since 1.218 */19 protected Class> findClass(String name) throws ClassNotFoundException{20 throw new ClassNotFoundException(name);21 }
(3)写方法loadClassData(String name)去读取我们的class数据(非必须)
1 /**2 * 自定义的加载器3 * Created by smlz on 2019/10/22.4 */5 public class TulingClassLoader extends ClassLoader {67 private final static String fileSuffixExt = ".class";89 private String classLoaderName;1011 private String loadPath;1213 public void setLoadPath(String loadPath) {14 this.loadPath = loadPath;15 }1617 public TulingClassLoader(ClassLoader parent, String classLoaderName) {18 /**19 * 指定当前类加载器的父类加载器20 */21 super(parent);22 this.classLoaderName = classLoaderName;23 }2425 public TulingClassLoader(String classLoaderName) {26 /**27 * 使用appClassLoader加载器 作为本类的加载器28 */29 super();30 this.classLoaderName = classLoaderName;31 }3233 public TulingClassLoader(ClassLoader classLoader) {34 super(classLoader);35 }3637 /**38 * 方法实现说明:创建我们的class 的二进制名称39 * @author:smlz40 * @param name: 类的二进制名称41 * @return:42 * @exception:43 * @date:2019/10/22 14:4244 */45 private byte[] loadClassData(String name) {46 byte[] data = null;47 ByteArrayOutputStream baos = null;48 InputStream is = null;4950 try {51 name = name.replace(".","");52 String fileName = loadPath+name+fileSuffixExt;53 File file = new File(fileName);54 is = new FileInputStream(file);5556 baos = new ByteArrayOutputStream();57 int ch;58 while (‐1 != (ch = is.read())){59 baos.write(ch);60 }61 data = baos.toByteArray();62 }catch (Exception e) {63 e.printStackTrace();64 }finally {65 try{66 if(null != baos) {67 baos.close();68 }69 if(null !=is) {70 is.close();71 }72 }catch (Exception e) {73 e.printStackTrace();74 }75 }7677 return data;78 }7980 protected Class> findClass(String name) throws ClassNotFoundException{81 byte[] data = loadClassData(name);82 System.out.println("TulingClassLoader 加载我们的类:===>"+name);83 return defineClass(name,data,0,data.length);84 }85 }
(4)特别需要注意:我们自定义的类加载器默认情况下的父类加载器是我们的系统AppClassLoader
代码证据:
1 public TulingClassLoader(String classLoaderName) {2 /**3 * 使用appClassLoader加载器 作为本类的加载器4 */5 super();6 this.classLoaderName = classLoaderName;7 }89 //调用super()的时候10 protected ClassLoader() {11 //在这里,把getSystemClassLoader()作为我们自定义类加载器的12 //父亲13 this(checkCreateClassLoader(), getSystemClassLoader());14 }1516 private ClassLoader(Void unused, ClassLoader parent) {17 this.parent = parent;18 if (ParallelLoaders.isRegistered(this.getClass())) {19 parallelLockMap = new ConcurrentHashMap<>();20 package2certs = new ConcurrentHashMap<>();21 domains =22 Collections.synchronizedSet(new HashSet());23 assertionLock = new Object();24 } else {25 // no finer‐grained lock; lock on the classloader instance26 parallelLockMap = null;27 package2certs = new Hashtable<>();28 domains = new HashSet<>();29 assertionLock = this;30 }31 }
(1)把我们的Person.class文件copy的指定的磁盘目录下。同时classpath下 存在我们的Person.class文件
1 /**2 * 证明系统类加载器就是我们的自定义类加载器的父类3 * Created by smlz on 2019/11/12.4 */5 public class AppClassLoaderIsCustomerClassLoaderParent {67 public static void main(String[] args) throws ClassNotFoundException {8 /**9 * 执行test1()方法的时候打印结果式我们的系统类加载器去加载我们的10 Person的,虽然我们是通过TulingClassLoader 去加载我们的Person.class11 但是由于双亲委派模型会委托我们的AppClassLoader去我们的classes路面下去12 加载Person.class由于我们的classes目录下存在我们的Person.class13 所以我们的Person.class被我们的AppClassLoader去加载.14151617 ===================================================18 若我们把classpath下的Person.class给删除掉,那么我们的19 TulingClassLoader尝试去加载我们的Person.class,由于双亲委派模型下会委托父类AppClassLoader20 加载,但是我们人工把类路径下的Person.class给删除掉了后,那么我们的AppClassLoader加载不了21 我们的Person.class,从而是由我们的TulingClassLoader去加载.22 **/23 test1();2425 }2627 //正常情况下,把我们的AppIsCustParentDemo放到D:smlz的目录下28 public static void test1() throws ClassNotFoundException {2930 TulingClassLoader tulingClassLoader = new TulingClassLoader("tulingClassLoader");31 //设置加载路径32 tulingClassLoader.setLoadPath("D:smlz");33 //通过自定义类加载器加载我们的AppIsCustParentDemo34 Class> targetClass = tulingClassLoader.loadClass("com.tuling.smlz.jvm.open.AppIsCustParentDemo");3536 System.out.println("targetClass 被class加载器加载..."+targetClass.getClassLoader());3738 }39 }
1 public class Person {23 private Person person;45 public void setPerson(Object person) {6 this.person = (Person) person;7 }8 }
1 public class Demo {2 //需要把我们的ClassPath下的Person.class给删除3 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {45 TulingClassLoader classLoader1 = new TulingClassLoader("tulingClassLoader1");6 classLoader1.setLoadPath("D:smlz");78 TulingClassLoader classLoader2 = new TulingClassLoader("tulingClassLoader2");9 classLoader2.setLoadPath("D:smlz");1011 //通过classLoader1加载我们的Person12 Class> class1 = classLoader1.loadClass13("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person");14 System.out.println("class1的类加载器:‐>"+class1.getClassLoader());1516 Class> class2 = classLoader2.loadClass17("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person");18 System.out.println("class2的类加载器:‐>"+class2.getClassLoader());1920 System.out.println("class1==class2:"+(class1==class2));2122 //模拟问题23 Object person = class1.newInstance();2425 Object person2 = class2.newInstance();2627 Method method = class1.getMethod("setPerson",Object.class);28 //会抛出类型转换错误29 method.invoke(person,person2);30 }31 }
(1)类加载器的全盘委托机制:比如我们的Person类是由我们的AClassLoader进行加载的,那么我们Person引用的Dog类就会委托给我们的A ClassLoader进行加载
1 public class Person {23 public Person() {4 System.out.println("Dog类是由我们的类加载器:‐>"+Dog.class.getClassLoader());5 }6 }78 public class Dog {9 }1011 public class MainTest {1213 public static void main(String[] args) {1415 Person person = new Person();16 System.out.println("Person的classLoader:‐>"+person.getClass().getClassLoader());1718 }19 }
(2)类加载器的命名空间
类加载器的命名空间 是有类加载器本身以及所有父加载器所加载出来的binary name(full class name)组成。
1)在同一个命名空间里,不允许出现二个完全一样的binary name。
2)在不同的命名空间种,可以出现二个相同的binary name。当时二 者对应的Class对象是相互不能感知到的,也就是说Class对象的类型是不一样的
3)子加载器的命名空间中的binary name对应的类中可以访问 父加 载器命名空间中binary name对应的类,反之不行
测试环境:我们的Person是由我们的自定义类加载器(把classpath下的Person.class删除,并且把Person.class copy到磁盘文件上)TulingClassLoader进行加载的,Dog 是由我们的AppClassLoader进行加载的. 我们在Person中访问Dog。
1 public class Dog {2 }34 public class Person {56 public Person() {7 new Dog();8 System.out.println("Dog的classLoader:‐‐>"+Dog.class.getClassLoader());9 }1011 }1213 public class TestDemo {14 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {15 TulingClassLoader classLoader = new TulingClassLoader("tulingClassLoader");16 classLoader.setLoadPath("D:smlz");17 Class> clazz = classLoader.loadClass("com.tuling.smlz.jvm.open.classloadernamespace.Person");18 clazz.newInstance();1920 System.out.println("Person的类加载器:"+clazz.getClassLoader());21 }22 }
测试环境:把我们的Person.class放置在C:ProgramFilesJavajdk1.8.0_131jreclasses这个目录下,那么我们的Person.class就会被我们的启动类加载器加载,而我们的Dog类是被AppClassLoader进行加载,我们的Person类 中引用我们的Dog类会抛出异常。
1 public class Dog {2 }34 public class Person {56 public Person() {7 new Dog();8 System.out.println("Dog的classLoader:‐‐>"+ Dog.class.getClassLoader());9 }10 }111213 public class TestDemo {1415 public static void main(String[] args) throws IllegalAccessException, InstantiationException {161718 System.out.println("Person的类加载器:"+Person.class.getClassLoader());1920 System.out.println("Dog的类加载器:"+Dog.class.getClassLoader());2122 Class> clazz = Person.class;23 clazz.newInstance();2425 }26 }2728 运行结果:29 Person的类加载器:null30 Dog的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac231 Exception in thread "main" java.lang.NoClassDefFoundError: com/tuling/smlz/jvm/open/ParentClassLoaderNotAccessSonClassLoader/Dog32 at com.tuling.smlz.jvm.open.ParentClassLoaderNotAccessSonClassLoader.Person.(Person.java:11)33 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeMethod)34 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)35 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)36 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)37 at java.lang.Class.newInstance(Class.java:442)38 at com.tuling.smlz.jvm.open.ParentClassLoaderNotAccessSonClassLoader.TestDemo.main(TestDemo.java:16)
场景:JDBC接口技术之SPI之应用。
类的首次主动使用会触发类的初始化。
1)调用静态方法
2)给静态变量赋值获取读取一个静态变量
3)反射 Class.forName
4)new 出一个对象
5)执行main方法的时候
6)初始化子类会初始化他的父类
本文福利:
私信回复 888 领取一套200页2020最新的Java面试题总结手册
欢迎大家一起交流,喜欢文章记得关注我点赞转发哟,感谢支持!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。