如果两个收集器之间存在连线,就说明它们可以搭配使用,图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器

Parallel 吞吐量优先算法

CMS(concurrent mark sweep)垃圾回收器

JDK1.5时引入,JDK9被标记弃用,JDK14被移除

可以怎么说吧,它是第一款真正意义上的并发收集器,第一次实现了用户线程和垃圾收集线程一起工作

设计目标就是尽快能地减少STW

工作于老年代

它算法层面采用的是标记清除算法,并且也会stw

整个垃圾回收过程分为四个阶段:初始标记,并发标记,重新标记,并发清除

初始标记(STW):暂时时间非常短,标记与GC Roots直接关联的对象。

并发标记(最耗时):从GC Roots开始遍历整个对象图的过程。不会停顿用户线程

重新标记:(STW):修复并发标记环节,因为用户线程的执行,导致数据的不一致性问题

并发清理(最耗时)

因为最耗时的并发标记和并发清理是并发操作,虽然初始和标记会STW,但整体是低延迟的

由于采用标记清除算法,所以会不可避免地出现内存碎片的问题。且在给新数据分配内存时,也无法采用指针碰撞,只能维护一个空闲的内存地址列表

那问题来了,既然标记清除会产生内存碎片,为啥不把算法换成标记整理呢

因为你整理内存时,原来的用户线程要怎么用?因为你整理时会移动对象,移动对象时是会STW的。

优缺点

优点:并发收集,低延迟

缺点:内存碎片问题,占CPU资源(吞吐量可能会有所降低),

G1垃圾回收器

在JDK1.7版本正式启用,是JDK 9以后的默认GC选项

为了在大内存的情况下也有短暂的STW,G1是java 7后引入的一个新的垃圾回收器

相比于其他分代收集器(G1物理不分代,但逻辑分代)的全量回收,G1采用了增量回收,每次只回收垃圾最

多的region,且实现了STW真正意义上的可控,大内存的毫秒级别的STW

并行和并发:

并行:回收期间多个GC线程可同时工作,但用户线程STW

并发:G1拥有跟应用程序交替执行的能力,不会在回收长期阻塞应用程序

不分代收集

物理不分代:将堆分为不同的region

逻辑分代:但不要求相同代之间内存连续的

空间整合

内存回收是以region为基本单位的。region之间为复制算法,但整体可以看成标记整理,可以避免内存碎

可预测的停顿时间模型

可以让用户指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不过N毫秒

可简化调优

第一步:开启G1 -XX:+UseG1GC

第二步:设置堆的最大内存 -XMS

第三部:设置最大停顿时间 -XX:MaxGCPauseMillis

分区region

每个区大小相等,根据堆实际大小而定,整体被控制在1MB到32MB之间,在且生命周期相同

H区??:用来存放大对象,如果对象超过1.5region,就会放到这里

为啥要这个区

有些短期的大对象进入老年代不太好,会触发FullGC

Minor GC:复制算法

Mixed GC:标记整理算法

mixed GC过程

1.初始标记(STW):只是标记一下GCRoot能直达的对象

2.并发标记:并发完成GCRoot引用链,标记可达对象

3.最终标记(STW):确认垃圾

4.筛选回收(STW):开辟最多5%堆空间的内存用于标记整理的数据交换

STW200ms最多回收10%的垃圾最多区域,且回收会检查老年代是否低于45%

没达标就重新来一次,最多8次,8次没达标就FULLGC

oldGC

如何选择垃圾回收器?

入门

说在前头

Spring封装好的重试工具类,可优雅实现接口重试逻辑

github直达:https://github.com/spring-projects/spring-retry

基本使用

为什么要额外学下这个:公司项目中的持久化框架用到这个注解,当时上手项目的时候比较感兴趣就去

翻了源码

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableRetry
public class Application {

}

@Service
class Service {
@Retryable(retryFor = RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
  • @EnableRetry 加在启动类,支持重试功能
  • @Retryable 加在重试的方法上
  • @Recover 重试完成后还是不成功的情况下,会执行被这个注解修饰的方法。

源码入手

因为是注解,不好定位到源码,我们可以通过日志来分析

org.springframework.retry.support.RetryTemplate#doExecute

分代收集理论,垃圾收集器的理论基础,它建立在两个分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的。
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

  • 如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;
  • 如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

Java堆划分为新生代(Young Generation)和老年代(Old Generation)两个区域。在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象, 将会逐步晋升到老年代中存放

标记-清除算法:

基础算法,后面两个算法基于此算法改进

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程。该算法两个缺点:

  • 执行效率不稳定
  • 内存碎片化

标记-复制算法:

适用于新生代的算法,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。算法缺点很明显:少了一半空间

新生代中的对象有98%熬不过第一轮收集。因此并不需要按照1∶1的比例来划分新生代的内存空间。

Appel式回收:把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用 Eden 和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一 次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor 空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。当然,98%的对象可被回收仅仅是 “普通场景”下测得的数据,任何人都没有办法百分百保证每次回收都只有不多于10%的 对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安全设计,当 Survivor 空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域 (实际上大多就是老年代)进行分配担保。

标记-整理算法:

针对老年代对象的存亡特征,1974年Edward Lueders提出了另外一种有针对性的 “标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

为什么标记-复制适用于新生代,标记-整理适用于老年代?

新生代需要被清理的对象多,复制只需要复制少量存活对象

老年代存活的对象多,不能使用Eden Survivor那套,不然内存就不够了,当然标记-整理也是一项很负重的操作,但如果不整理,就需要额外使用页表等方式标记哪些空间可用来解决空间碎片化问题,这也会导致额外负担,所以从整个程序的吞吐量考虑,标记-整理是较好的选择

当然老年代也可以先标记-清除,等内存空间碎片化到一定程度时,进行一次标记整理

三色标记法:

可达性分析算法中,标记过程需要stop the world,保证全局获得一致性快照,这个操作可否与用户线程并发?

先来看三色标记法:把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达,需要被回收
  • 黑色:表示对象已经被垃圾收集器访问过,且所有引用了这个对象的对象都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,不用被回收,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。是个中间态,当扫描完成后,不会出现这个颜色,整个图非黑即白

可达性分析算法其实就是从GC Roots出发,将图(对象直接的引用关系图)波浪式地由白色转为黑色,其中灰色是黑白之间的过渡色。

这个标记法在用户线程和收集器并发工作下可能存在问题:

  • 是把原本消亡的对象错误标记为存活,这不是好事,但其实是可以容忍的,只不过产生了一点逃过本次收集的浮动垃圾而已,下次收集清理掉就好。
  • 把原本存活的对象错误标记为已消亡,这就是非常致命的后果了,程序肯定会因此发生错误

当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即原本应该是黑色的对象被误标为白色,导致回收应该存活的对象:

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用;即对象A被其它已扫描过的且安全存活的对象链上了
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。即所有引用A且正在被扫描的对象取消了对A的引用

因此,我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。

当标记与用户线程并发时,可能造成以上问题,为了使标记与用户线程并发,减少STW的时间,就需要解决上述问题,由此分别产生了两种解决方案:增量更新 和 原始快照

  • 增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。即出现了新的引用关系就记录下来,然后重新扫描
  • 原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再以这些灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。(当B删除了N引用之后,B->N 的关系仍然被记录,这个动作通过一个写屏障来实现(可以理解为一个aop)。扫描结束之后再以B为根(被记录的灰色对象为根)重新扫描一次,此时的扫描的B->N的引用已经被重新记录了,即使他实际已经被删除但在这次扫描中它仍然存在。但是这可能导致N在本次垃圾回收时应该被回收,却逃过了这次,不过没关系,下次gc它逃不了)

原始快照相对于增量更新更快,但是可能产生更多的浮动垃圾

JVM—理解G1的SATB和CMS的增量更新

增量更新和原始快照这两种解决方案都有实际应用,譬如,CMS是基于增量更新来做并发标记的,G1、Shenandoah则是用原始快照来实现。

新生代Minor GC流程:

  • 当Eden区满时,触发Minor GC
  • 标记算法找到所有存活下来的对象
  • 检查老年代最大可用的连续空间是否大于新生代所有存活下来的对象的空间,如果大于,则发起 Minor GC。
  • 如果小于,则看 HandlePromotionFailure 有没有设置,如果没有设置,就发起 Full GC。
  • 如果设置了 HandlePromotionFailure,则看老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于,就发起 Full GC。

如果大于,发起 Minor GC。Minor GC 后,看 Survivor 空间是否足够存放存活对象,如果不够,就放入老年代,如果够放,就直接存放 Survivor 空间。如果老年代都不够放存活对象,担保失败(Handle Promotion Failure),发起 Full GC。

HandlePromotionFailure(是否允许进行晋升担保) 的作用,当设置为 true 时(默认值),JVM 会尝试继续 Minor GC,即使老年代空间不足以容纳所有需要晋升的对象。JVM 会尝试清理更多的老年代空间或者采用其他措施来应对空间不足的情况。避免因为老年代空间不足而过早触发 Full GC(全堆回收)。Full GC 通常比 Minor GC 更耗时,会导致更长时间的停顿。但该参数在JDK7开始彻底被弃用,相当于该参数变为false,这可能导致Full GC频繁发生,但JVM也开始动态调整新生代、老年代的空间大小配置,尽量减少Full GC的发生,且如果老年代空间不足,会提前执行部分清理或混合GC

方法区里存的是类的结构信息啥的,而它是很难被卸载,因为它对应的堆中的class对象与类加载器有着双向关联的关系,而类加载器会维护一个java集合,里面标明着哪些类是被该类加载器加载的,如果要把该类加载器干掉,其他类也会被干掉,那这是不现实的,也就只存在该类加载为自定义类加载器。所以该类的结构信息是很难被回收的,为了防止堆溢出,所以就把它搬到了本地内存中

可达性分析算法思想

jvm里是通过可达性分析算法分析对象是否可以被回收,从GCRoot出发搜索,哪些无法被搜索到哪些就是要

被回收的对象

什么的对象可以当做GCRoot?

总的来说,因为jvm中是用栈来保存变量和引用,那么只要这个引用保存了堆里的对象, 且它(引用)不

在堆里,那么它就可以作为一个GCRoot

展开来说有这么几种

1.虚拟机栈中的引用对象

2.类静态属性的引用对象

3.方法区常量引用的对象

4.所有被同步锁即synchronized持有的对象

5.jvm内部的引用,如一些常驻异常对象,如NPE对象,OOM对象

…..

使用可达性分析的时候,jvm必须处于一个一致性快照的状态,这也是导致GC过程中stop the world的原因之一

对象自救,在可达性分析算法过程中被判定为不可达的对象一定是“非死不可的吗?”

不一定。对象在第一次回收时被判定为不可达,可以通过重写finalize()的逻辑来使对象复活,达到一种自

救的目的。如果不重写那么就会直接死亡了,还有就是这个方法最多只会被jvm调用一次

ps:finalize()是对象被回收时会去调用的一个方法,我们在里面做一些对象自救或者资源释放的工作

三色标记法??

三色标记法就是可达性分析算法的落地实现

https://juejin.cn/post/6979249945551339551

三色标记法是为了解决可达性分析算法的STW过长问题,是基于它进行一个升级。CMS,G1垃圾回收器可达

性分析的过程中都用到了

其实就是说用三种颜色来表示对象在回收过程的不同状态

白色:还没被扫描到的对象

灰色:已经被扫描到的对象但其子引用还没被扫到

黑色:当前对象以及其子引用都被扫描到了

它是怎么执行的???

总体分为三个阶段吧:初始,标记,回收

初始:把对象都标记为白色,然后会从GCRoot出发把直接可达对象标记为灰色,并加入到一个集合里

标记:从集合里拿出灰色对象,把这些对象引用的白色对象标记为灰色,最后再把这个对象本身标记为黑

色。重复这个过程,直到为空了

回收:当灰色集合里的对象空了后,就会进入这个阶段了。那这时候就很清楚了,白色集合里的对象都是

不可达的,而黑色都是可达的

优点

在并发环境下确保垃圾回收的安全性和正确性,同时尽可能减少停顿时间(STW)

如果重新标记后又发生了引用更改呢?

回收阶段会STW


通过可达性分析算法判定对象是否存活:

通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
  • Java 虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象 (比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4 种

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在, 垃圾收集器就永远不会回收掉被引用的对象,即使OOM。
  • 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2版之后提供了 SoftReference 类来实现软引用。
  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2版之后提供了 WeakReference 类来实现弱引用。
  • 虚引用是最弱的一种引用关系。虚引用和引用队列联合使用,用来追踪对象的回收情况。虚拟机回收对象时,如果发现对象还存在虚引用,会在回收对象后将引用加入到关联的引用队列中。程序可以通过观察引用队列的方式,来感知对象即将被垃圾回收的时机。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。

对象自救:

即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”,没有必要执行则直接回收。

如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的 finalize() 方法。

finalize()方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了

两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况

如果自定义一个com.example.String会怎样?

可以自定义,项目中的String的优先级是同包下的String>java.lang包下的String>自定义包下的String,然后如果一个类中用到了两种String,需要指定好,比如main函数的入参String[] args

双亲委派模型:

  • 启动类加载器(Bootstrap Class Loader):负责加载存放在 \lib目录,或者被-Xbootclasspath参数所指定的路径中存放的, 而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中,其实就是JDK库里的
  • 扩展类加载器(Extension Class Loader):负责加载 \lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库(JDK9及以后,被重命名为平台类加载器Platform ClassLoader),可能是一些旧版的加密库等等,现在项目很少用到
  • 应用程序类加载器(Application Class Loader):负责加载用户类路径(ClassPath)上所有的类库,就是我们写的代码,例如com.example.xxx和第三方jar包

双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合 (Composition)关系来复用父加载器的代码。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时, 子加载器才会尝试自己去完成加载。

为什么使用双亲委派:

因为Java是确定一个唯一的类是根据类的全路径+类加载器确定的,对于同一个包名路径下的类,如果被不同类加载器加载出来,就会产生不同的类,这时程序的行为就不能保证了,例如,自己写一个java.lang.Object,并放在程序的ClassPath中,如果没有双亲委派,程序中就出现两个java.lang.Object类了(自己写的和java官方的,分别由应用程序类加载器和启动类加载器加载),但如果使用了双亲委派,即使一开始是由应用程序类加载器加载,最终也会委托给启动类加载器,启动类加载器根据类的全路径发现自己已经加载过这个类了,就不会再加载这个类,这就保证了核心类库里的类都是唯一且正确的,所以双亲委派模型保证了Java程序的稳定运作

为什么要向上委派,不能向下委派?

是否能一开始就由启动类加载器加载所有的类,启动类加载器发现不在自己加载范围内的类就交给自己的子类加载器?答案是不行的,因为一个类加载器可以只有一个父类加载器,需要加载类时直接向上委派就行,但如果是向下委派,同时又有多个子类加载器,这时候就不知道要委派给哪个子类加载器的(可能有多个子类加载器的原因是因为可以有自定义类加载器,加载器的组合是多样的,但不管怎样,只可能有一个父类加载器),这就好比一个二叉树,从叶子节点向上走,是有且只有一条唯一的路径的,而且这条路径的终点必然是根节点,而从根节点出发,一直向下走,是不能确定一条唯一路径的,虽然最终能到达某一叶子节点,但具体是哪个叶子节点取决于每次向下走的路线决策。而且向下委派意味着需要修改应用程序类加载器的源码。

破坏双亲委派模型:

自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

Tomcat采用自定义类加载器破坏了双亲委派,实现了Web应用之间的类的隔离

JDBC接口是jre下的,但实现是第三方供应商提供的,按理来说,一个类及其依赖类由同一个类加载器加载(确保这些类之间的依赖关系正确并保持一致),但这种情况下不会被同一个类加载器加载,这就需要用到线程上下文类加载器解决

线程上下文类加载器:当一个线程启动时,jvm会将应用类加载器赋值给当前线程的线程上下文类加载器,此时父类加载器就可以提高线程上下文类加载器获取到子类加载器,再由子类加载器加载父类找不到的类

什么时候需要自定义类加载器:

  • 想加载非classpath随意路径的类文件
  • 隔离同名类,需要不同应用下的同名类可以不冲突,如tomcat容器

抽象类是为了复用代码,如模版方法。而接口主要是为了定义规范

抽象类

1.7以前,抽象类的方法默认权限为protected

1.8开始,抽象类的方法默认为default了

接口

接口的成员变量只能为public static final

1.7接口的方法只能有public abstract

1.8接口的方法可以被default修饰,但得有方法体

1.8接口的方法可以被static修饰,但得有方法体,且不能和default一起使用

1.9接口的方法可以被private修饰,但得有方法体,且同时可以有static,但不能用default