类加载器有哪四个(谈谈类加载器吧)
Java类加载器是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
文末有福利~
1、在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)层级结构
二、类加载器加载我们的Class的时候遵循我们的双亲委派模型
在双亲委派机制中,各个加载器按照父子关系形成树型结构,除了根加载器以外,每一个加载器有且只有一个父加载器
1、源码分析:
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
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 //调用父类的loadClass
14 c = parent.loadClass(name, false);
15 } else {//不存在父类,表示当前的classLoader是extClassLoader
16 //那么就会调用启动类判断是否加载过
17 c = findBootstrapClassOrNull(name);
18 }
19 } catch (ClassNotFoundException e) {
20 // ClassNotFoundException thrown if class not found
21 // from the non‐null parent class loader
22 }
23 //到目标位置,app ext boot都没有去加载过
24 if (c == null) {
25 // If still not found, then invoke findClass in order
26 // to find the class.
27 long t1 = System.nanoTime();
28 //委托我们的子类的classLoader去找
29 c = findClass(name);
30
31 // this is the defining class loader; record the stats
32 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 }
3、类加载器的双亲委派模型的好处:
总所周知:java.lang.object类是所有类的父类,所以我们程序在运行期间会把java.lang.object类加载到内存中,假如java.lang.object类能够被我们自定义类加载器去加载的话,那么jvm中就会存在多份Object的Class对象,而且这些Class对象是不兼容的。
所以双亲委派模型可以保证java核心类库下的类型的安全。
借助双亲委派模型,我们java核心类库的类必须是由我们的启动类加载器加载的,这样可以确保我们核心类库只会在jvm中存在一份这就不会给自定义类加载器去加载我们核心类库的类。
根据我们的演示案例,一个class可以由多个类加载器去加载,同事可以在jvm内存中存在多个不同版本的Class对象,这些对象是不兼容的。
4、如何手写一个自定义类加载器(根据ClassLoader的doc文档)
(1)我们自定义类加载器必须要继承ClassLoader
(2)我们必须要findClass(String name)方法
1 /**
2 * Finds the class with the specified <a href="#name">binary name</a>.
3 * This method should be overridden by class loader implementations that
4 * follow the delegation model for loading classes, and will be invoked b
y
5 * the {@link #loadClass <tt>loadClass</tt>} method after checking the
6 * parent class loader for the requested class. The default implementatio
n
7 * throws a <tt>ClassNotFoundException</tt>.
8 *
9 * @param name
10 * The <a href="#name">binary name</a> of the class
11 *
12 * @return The resulting <tt>Class</tt> object
13 *
14 * @throws ClassNotFoundException
15 * If the class could not be found
16 *
17 * @since 1.2
18 */
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 {
6
7 private final static String fileSuffixExt = ".class";
8
9 private String classLoaderName;
10
11 private String loadPath;
12
13 public void setLoadPath(String loadPath) {
14 this.loadPath = loadPath;
15 }
16
17 public TulingClassLoader(ClassLoader parent, String classLoaderName) {
18 /**
19 * 指定当前类加载器的父类加载器
20 */
21 super(parent);
22 this.classLoaderName = classLoaderName;
23 }
24
25 public TulingClassLoader(String classLoaderName) {
26 /**
27 * 使用appClassLoader加载器 作为本类的加载器
28 */
29 super();
30 this.classLoaderName = classLoaderName;
31 }
32
33 public TulingClassLoader(ClassLoader classLoader) {
34 super(classLoader);
35 }
36
37 /**
38 * 方法实现说明:创建我们的class 的二进制名称
39 * @author:smlz
40 * @param name: 类的二进制名称
41 * @return:
42 * @exception:
43 * @date:2019/10/22 14:42
44 */
45 private byte[] loadClassData(String name) {
46 byte[] data = null;
47 ByteArrayOutputStream baos = null;
48 InputStream is = null;
49
50 try {
51 name = name.replace(".","\");
52 String fileName = loadPath name fileSuffixExt;
53 File file = new File(fileName);
54 is = new FileInputStream(file);
55
56 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 }
76
77 return data;
78 }
79
80 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 }
8
9 //调用super()的时候
10 protected ClassLoader() {
11 //在这里,把getSystemClassLoader()作为我们自定义类加载器的
12 //父亲
13 this(checkCreateClassLoader(), getSystemClassLoader());
14 }
15
16 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<ProtectionDomain>());
23 assertionLock = new Object();
24 } else {
25 // no finer‐grained lock; lock on the classloader instance
26 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 {
6
7 public static void main(String[] args) throws ClassNotFoundException {
8 /**
9 * 执行test1()方法的时候打印结果式我们的系统类加载器去加载我们的
10 Person的,虽然我们是通过TulingClassLoader 去加载我们的Person.class
11 但是由于双亲委派模型会委托我们的AppClassLoader去我们的classes路面下去
12 加载Person.class由于我们的classes目录下存在我们的Person.class
13 所以我们的Person.class被我们的AppClassLoader去加载.
14
15
16
17 ===================================================
18 若我们把classpath下的Person.class给删除掉,那么我们的
19 TulingClassLoader尝试去加载我们的Person.class,由于双亲委派模型下会委托父类A
ppClassLoader
20 加载,但是我们人工把类路径下的Person.class给删除掉了后,那么我们的AppClassLo
ader加载不了
21 我们的Person.class,从而是由我们的TulingClassLoader去加载.
22 **/
23 test1();
24
25 }
26
27 //正常情况下,把我们的AppIsCustParentDemo放到D:\smlz的目录下
28 public static void test1() throws ClassNotFoundException {
29
30 TulingClassLoader tulingClassLoader = new TulingClassLoader("tulingClas
sLoader");
31 //设置加载路径
32 tulingClassLoader.setLoadPath("D:\smlz\");
33 //通过自定义类加载器加载我们的AppIsCustParentDemo
34 Class<?> targetClass = tulingClassLoader.loadClass("com.tuling.smlz.jv
m.open.AppIsCustParentDemo");
35
36 System.out.println("targetClass 被class加载器加载..." targetClass.getCla
ssLoader());
37
38 }
39 }
1 public class Person {
2
3 private Person person;
4
5 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, Il
legalAccessException, InstantiationException, NoSuchMethodException, Invoca
tionTargetException {
4
5 TulingClassLoader classLoader1 = new TulingClassLoader("tulingClassLoade
r1");
6 classLoader1.setLoadPath("D:\smlz\");
7
8 TulingClassLoader classLoader2 = new TulingClassLoader("tulingClassLoade
r2");
9 classLoader2.setLoadPath("D:\smlz\");
10
11 //通过classLoader1加载我们的Person
12 Class<?> class1 = classLoader1.loadClass
13
("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person");
14 System.out.println("class1的类加载器:‐>" class1.getClassLoader());
15
16 Class<?> class2 = classLoader2.loadClass
17
("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person");
18 System.out.println("class2的类加载器:‐>" class2.getClassLoader());
19
20 System.out.println("class1==class2:" (class1==class2));
21
22 //模拟问题
23 Object person = class1.newInstance();
24
25 Object person2 = class2.newInstance();
26
27 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 {
2
3 public Person() {
4 System.out.println("Dog类是由我们的类加载器:‐
>" Dog.class.getClassLoader());
5 }
6 }
7
8 public class Dog {
9 }
10
11 public class MainTest {
12
13 public static void main(String[] args) {
14
15 Person person = new Person();
16 System.out.println("Person的classLoader:‐>" person.getClass().getClassLo
ader());
17
18 }
19 }
(2)类加载器的命名空间
类加载器的命名空间 是有类加载器本身以及所有父加载器所加载出来的binary name(full class name)组成。
1)在同一个命名空间里,不允许出现二个完全一样的binary name。
2)在不同的命名空间种,可以出现二个相同的binary name。当时二 者对应的Class对象是相互不能感知到的,也就是说Class对象的类型是不一样的
3)子加载器的命名空间中的binary name对应的类中可以访问 父加 载器命名空间中binary name对应的类,反之不行
8、验证子加载器加载出来的类可以访问父加载器加载的类
测试环境:我们的Person是由我们的自定义类加载器(把classpath下的Person.class删除,并且把Person.class copy到磁盘文件上)TulingClassLoader进行加载的,Dog 是由我们的AppClassLoader进行加载的. 我们在Person中访问Dog。
1 public class Dog {
2 }
3
4 public class Person {
5
6 public Person() {
7 new Dog();
8 System.out.println("Dog的classLoader:‐‐>" Dog.class.getClassLoader());
9 }
10
11 }
12
13 public class TestDemo {
14 public static void main(String[] args) throws ClassNotFoundException, I
llegalAccessException, InstantiationException {
15 TulingClassLoader classLoader = new TulingClassLoader("tulingClassLoade
r");
16 classLoader.setLoadPath("D:\smlz\");
17 Class<?> clazz = classLoader.loadClass("com.tuling.smlz.jvm.open.classl
oadernamespace.Person");
18 clazz.newInstance();
19
20 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 }
3
4 public class Person {
5
6 public Person() {
7 new Dog();
8 System.out.println("Dog的classLoader:‐‐>" Dog.class.getClassLoader());
9 }
10 }
11
12
13 public class TestDemo {
14
15 public static void main(String[] args) throws IllegalAccessException, I
nstantiationException {
16
17
18 System.out.println("Person的类加载器:" Person.class.getClassLoader());
19
20 System.out.println("Dog的类加载器:" Dog.class.getClassLoader());
21
22 Class<?> clazz = Person.class;
23 clazz.newInstance();
24
25 }
26 }
27
28 运行结果:
29 Person的类加载器:null
30 Dog的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
31 Exception in thread "main" java.lang.NoClassDefFoundError: com/tuling/sm
lz/jvm/open/ParentClassLoaderNotAccessSonClassLoader/Dog
32 at com.tuling.smlz.jvm.open.ParentClassLoaderNotAccessSonClassLoader.Pe
rson.<init>(Person.java:11)
33 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native
Method)
34 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstruc
torAccessorImpl.java:62)
35 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Delegating
ConstructorAccessorImpl.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.Te
stDemo.main(TestDemo.java:16)
场景:JDBC接口技术之SPI之应用。
类的首次主动使用会触发类的初始化。
1)调用静态方法
2)给静态变量赋值获取读取一个静态变量
3)反射 Class.forName
4)new 出一个对象
5)执行main方法的时候
6)初始化子类会初始化他的父类
本文福利:
私信回复 888 领取一套200页2020最新的Java面试题总结手册
最后
欢迎大家一起交流,喜欢文章记得关注我点赞转发哟,感谢支持!
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。