前言:
接着上一次https://www.cnblogs.com/webor2006/p/16609029.html的继续往下,距离上一篇已经过去快半年了,从我的博文记录中就可以清楚地看到:
转眼2023年新春假期接近尾声了,在这近半年的时间里,其实发生了很多事,有伤心、有焦虑,当然也有开心和希望,其中还经历过人生中最最艰难的时期,还好在朋友的帮助下给抗过来了【关于这些故事这里就不分享了,默默藏在心里就好】。当然最最让我伤心的是,坚持了这么久的博客彻底地被放弃了,当你没有了节奏感之后,再拣回来是需要付出很大的代价的,这不新年来了嘛,必须要有个新气象,新气象打算先从恢复博客开始,这里接着Android面试题的系列章节继续往下,好,费话不多说,直接开干。
题面解析:
关于这道题,从题面来看,很显然是考ClassLoader相关的东东,而一谈到ClassLoader,就会想到它的双亲委派模型,关于这块的基础可以参考我之前所记录的https://www.cnblogs.com/webor2006/p/9016996.html,说实话这块基本学了就忘,所以正好可以借此机会再来复习一下。其中这里有两个加粗的词:“ClassLoader”、“双亲委派模型”,是的,要想答好此题,需要从这俩角度来进行剖析,那都有哪些点呢,下面列一下:
ClassLoader:
对于它,主要需要掌握如下知识点:
1、它是做什么的?
2、它的加载过程是怎么样的?
3、它把class加载给谁?
双亲委派模型:
对于它,则需要从双亲委派模型的“源码”出发,然后再来掌握它的原理。
总结:
当以上两个知识点都掌握之后,接下来还有一些可以说的:
1、说一下ClassLoader和双亲委派模型这样设计的一个好处?
2、双亲委派模型它是一种规则,而有些情况下是需要打破这种双亲委派模型的,那如何打破呢?
是不是这么一解析,要想答好这道题,其实不是那么简单的,而如果按照这么一个思路去解答,就会做到有理有据。
本题得分点:
接下来看一下得分点,还是如之前所学习的,从以下两个角度来看:
知识储备:
主要是ClassLoader、方法区、双亲委派模型。
技术思考:
1、双亲委派解决了什么问题?
2、如何打破双亲委派模型?
字节码加载:
先来回顾一下字节码class文件加载的过程, 如下图:
可以看到.class字节码文件是通过ClassLoader来加载到运行时数据区的“方法区”中的。
方法区:
那方法区主要是作用是啥呢?其实就是在内存中,存放class文件的逻辑结构,也就是类的元(meta)信息, 其中就包括在上一次https://www.cnblogs.com/webor2006/p/16609029.html所学习到的:常量池、类信息、字段、方法、属性等。
方法区实现:
我们知道JVM只是一个规范,而方法区其实也只是一个规范,它有不同的实现方式。
- 在Java8以前的版本,被实现“永久代”,名称与堆中的“年轻代”、“养老代”相对应,和堆一样,同为线程共有,但又没有垃圾回收【这个比较容易理解,因为方法区是存放的类的元(meta)信息,如果它能够被回收,是不是意味着我们就不能创建类的实例了?】,所以又被称为“非堆”。
- 在Java8以后的版本,被称为元空间(meta space),直接放在本地内存,所以理论上没有大小上限。
Java程序的双亲委派模型:
先来了解Java程序的双亲委派模型,说到“Java程序”,很明显对于Android来说还有它自身的双亲委派模型,这个之后就会谈到,这里先来看下Java程序的双亲委派,先上个图:
关于这块的介绍在之前https://www.cnblogs.com/webor2006/p/9016996.html已经详细有说明,这里就当大概复习一下核心点,对于要注意:从图中貌似看这些加载类是一个父子的树形结构对吧,其实实际不是:
而标红的是三大系统加载类,最底层的是自己自定义的加载类,对于它们加载的职责也是不一样的,简单说明一下:
其中位于jdk中的rt.jar熟悉吧,它里面的类就是由Bootstrap ClassLoader来进行加载的,如我们本题所探讨的java.lang.Object这个类。
Android程序的双亲委派模型:
概述:
好,接下来则来看一下Android的双亲委派模型了,这块也是咱们最关注的,肯定是跟Java有些不同点的,先上张图:
对比Java的双亲委派模型,系统类加载器由三个变成了二个了,其中PathClassLoader类似于Java中的Application ClassLoader,而自定义ClassLoader变成了DexClassLoader了,很明显在Android中加载的是dex文件了,
类图:
在正式看这块源码之前,先来看一下整体的双亲委派的类图,如下:
ClassLoader源码:
findClass():
好,接下来则先从ClassLoader的源码开始分析,这里只分析主流程,一些不相关的直接省略掉有助于程序的理解,先从入口开始:
这个类应该也是普遍比较熟悉的,它的具体实现是由子类来决定的,这样就使得不同层级的ClassLoader就可以实现去不同的位置加载类的需求了,比如启动类加载器就可以加载jre-lib下的class,应用加载器就可以加载我们工程编译的class,
loadClass():
接下来另一个重要方法就是加载类的方法了,先来看一下它的实现:
这块实现也是大家比较熟悉的了,其中很明显是先读取缓存,如果该类已经被加载了则直接返回,如果木有被加载,则会递归调用父类加载器的loadClass:
而如果父类加载器都加载不到该类,则会交由当前的类加载器进行加载:
其双亲委派的机制就源自于这个类加载器的源代码逻辑。
加载自己写的Object类会发生什么?
好,在了解了双亲委派机制之后, 接下来我们就可以来分析一下,如果自己写了一个java.lang.Object的类,会发生啥?
1、自定义DexClassLoader会递归委派给父类加载器进行加载:
也就是:
接下来父类都未加载过,则会递归委派,如下:
2、启动类加载器会加载framework中的Object类:
根据源码,由于启动类加载器已经是顶层加载器,木有父类了,则此时就会交由自身进行加载,而启动类加载器就会加载framework中的Object类了:
3、总结:
通过上面的分析, 是不是发现,有了这个双亲委派模型,就始终都未执行过自定义DexClassLoader的findClass()了,加载的永远是android framework层的class了?所以很明显对于这题的答案就是“能自己写一个java.lang.Object的类,但是永远不会被加载进来,因为java.lang.Object是系统中的类,会被启动类加载。”
Class的双亲委派模型有什么好处?
接下来再来看一下对于有了双亲委派模型之后,它带来的好处:
1、能够对类划分优先级层次关系;
我们自己写的类,永远都是要比系统的类层次要低的。
2、避免类的重复加载;
一旦加载了某个类,相关的子类加载器就没有机会重复加载这个类了。
3、沙箱安全机制,避免代码被篡改:
系统的核心类库对于外界来说就是一个沙箱,我们无法通过正常的代码来干涉核心类库的执行,同样的自定义类加载器也无法干预我们写的应用程序的代码。
为什么要打破双亲委派模型呢?
对于双亲委派模型有这么多好处,那何时需要打破这种它,下面举几个例子:
1、解决某些版本冲突问题:
比如:
也就是B类加载器加载了某类库的1.0版本,但是呢对于它的子类加载器c必须要使用该库的2.0的版本,如果按照双亲委派的原则,很明显是办不到的,因为C加载器永远没有办法加载2.0的版本了,此时双亲委派模型就需要打破了,可以这么做:
也就是让子类C加载器越过加载器B,直接成为加载器A的子类加载器,此时就可以让C类加载器使用2.0的版本了,当然前提是子类加载器B和C木有过多的关联关系。
注意: 这只是一个理论上可行的方案【也就是在面试时让面试官了解自己是懂类加载器机制的】,实际开发中有更好的方式来解决版本冲突,比如使用gradle的脚本。
2、 热部署(重复加载已经被加载的类):
这个需求很明显就已经违背了双亲委派模型了【因为已经加载的类,是不允许再次加载的】,此时就需要打破双亲委派了,下面具体来看一下这种场景:
比如有一个热部署的类叫Operation,然后做法就是用一个子类加载器C来加载:
注意,此时C加载器不能成为B类加载器直接或间接的子类加载器,此时C加载器就可以重新加载Operation了,另外它只能在子ClassLoader C加载的类范围内生效:
只能说是一个有限的热部署,这里也是仅限面试时可以提一下,而对于Android来说,通常会想到热更新对吧,它那实现就比较复杂,比如Tinker,关于它的思想可以参考之前学习的这篇:https://www.cnblogs.com/webor2006/p/10708754.html。
解题总结:
问:你能不能自己写一个叫做java.lang.Object的类?
答:能写,能通过编译,但是不会被加载,其不被加载的原因是由于有类加载的双亲委派模型所决定【这里就可以展开对双亲委派机制的描述了】,而如果想要被加载就需要打破双亲委派模型了【这里就又可以展开打破双亲委派机制的知识点进行阐述了】。
标签:lang,03,java,ClassLoader,Object,委派,双亲,模型,加载 From: https://www.cnblogs.com/webor2006/p/16635668.html