面试官:谈谈类加载器吧,你有没有看过类加载器的源码

一、类加载

1.1、在java代码中,类型的加载,连接,初始化过程都是在程序运行期间完成的。

图示:

1.2、类型的加载——这里的类型是指的什么?

答:类型就是指的我们Java源代码通过编译后的class文件。

1.3、类型的来源有哪些?

(1)本地磁盘

(2)网络下载,class文件

(3)war,jar下加载,class文件 

(4)从专门的数据库中读取,class文件(少见) 

(5)将java源文件动态编译成class文件 

1)典型的就是动态代理,通过运行期生成class文件 

2)我们的jsp会被转换成servlet,而我们的serlvet是一个java文件,会被编译成class文件

1.4、通过什么来进行加载?(类加载器)

1.5、类加载的分类以及各种加载职责以及层级结构

(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 }

2、双亲委派模型加载的流程图

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 }

5、怎么用实验证明我们的自定义类加载器的父加载器就是系统类加载器

(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 }

6、同一个Person.class文件 被我们的不同的类加载器去加载,那么我们的jvm内存种会生成二个对应的Person的Class对象,而且这二个对应的Class对象是相互不可见的(通过Class对象反射创建的实例对象相互是不能够兼容的不能相互转型) 这一点也很好的解释了

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 }

 

7、类加载器的全盘委托机制以及 类加载器的命名空间

(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 }

9、如何证明父加载加载的类不能访问子加载器加载的类 

测试环境:把我们的Person.class放置在C:\ProgramFiles\Java\jdk1.8.0_131\jre\classes这个目录下,那么我们的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)

 

10、打破双亲委派模型之 线程上下文类加载器

场景:JDBC接口技术之SPI之应用。

类的首次主动使用会触发类的初始化。

1)调用静态方法 

2)给静态变量赋值获取读取一个静态变量 

3)反射 Class.forName 

4)new 出一个对象 

5)执行main方法的时候 

6)初始化子类会初始化他的父类

最后

关注公众号:程序员追风。回复 003 领取2020最新Java面试题手册(200多页PDF文档)

 

posted on 2020-07-28 14:48  Java架构师追风  阅读(793)  评论(0编辑  收藏  举报

导航