java面试准备之JVM及设计模式

JVM

一、内存分区

1.1 程序计数器

内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
1.2 Java 虚拟机栈

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
1.3 本地方法栈

区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

1.4 Java 堆

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
1.5方法区

属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

1.6 运行时常量池

属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

1.7 直接内存

非虚拟机运行时数据区的部分
注:
JVM的方法区和永久带是什么关系?
永久带又叫Perm区,只存在于hotspot jvm中,并且只存在于jdk7和之前的版本中,jdk8中已经彻底移除了永久带,jdk8中引入了一个新的内存区域叫metaspace。
(1)并不是所有的jvm中都有永久带,ibm的j9,oracle的JRocket都没有永久带。
(2)永久带是实现层面的东西。
(3)永久带里面存的东西基本上就是方法区规定的那些东西。
我们可以说,永久带是方法区的一种实现,当然,在hotspot jdk8中metaspace可以看成是方法区的一种实现。

二、 垃圾回收机制

主要负责两件事情:
(1)发现无用的对象;
(2)回收被无用对象占用的内存空间,使之再次被程序使用(一般是在CPU空闲或者内存不足时)。
注:事实上,除了释放没用对象占用的内存空间外,垃圾回收也可以清除内存纪录碎片(由于创建对象和垃圾回收器释放丢弃对象所占的内存空间)

三、 gc常见算法

引用计数、可达性分析算法判断对象是否死亡
标记-清除算法、复制算法、标记-整理算法、分代收集算法 垃圾回收的四种算法**
3.1 引用计数法
简单但是速度很慢,缺陷是不能处理循环引用的情况。
原理:此对象有一个引用,既增加一个计数器,删除一个引用减少一个计数器,垃圾回收时,只回收计数器为0的对象,此算法最致命的 是无法处理循环引用的情况。
3.2可达性分析算法(根搜索算法)
可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。
从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。
在Java语言中,可以作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中的引用对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI(Native方法)的引用对象
真正标记以为对象为可回收状态至少要标记两次。
finalize()方法 最后的救赎
四种引用
强引用就是指在程序代码之中普遍存在的,类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
Object obj = new Object();
软引用是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
虚引用也成为幽灵引用或者幻影引用,它是最弱的一中引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供给了PhantomReference类来实现虚引用。

3.3 标记-清除算法
标记清除算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
之所以说他是最基础的收集算法,是因为后续的收集算法都是基于这种思路并且对其不足进行改进而得到的。
它的主要不足有两个:

  1. 一个是效率问题,标记和清除两个过程的效率都不高(效率不高
  2. 另一个是空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续的内存而不得不提前触发另一次垃圾收集动作。(产生大量空间碎片

3.4 复制算法
把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。(内存利用率只有一半)
解决前一种方法的不足,但是会造成空间利用率低下。因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。
3.5 标记-整理算法
不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是把存活对象移到内存的一端
3.6 分代收集算法
根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。
新生代
每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。
老年代
老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除 或者 标记 —— 整理 算法回收。
3.7 系统线程划分
串行收集器
使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势。所以此收集器适合单处理器的机器。当然,此收集器也可以用在小数量(100M左右)情况下的多处理器机器上,可以使用-XX:+UseSerialGC打开。
并行收集器
对年轻代进行并行垃圾回收,因此可以减少垃圾回收的时间,一般在多线程多处理器上使用,使用-XX:+UseParallelGC打开。年老代进行并行收集。如果年老代不使用并发收集的话,是使用单线程进行垃圾回收,因此会制约扩展能力,使用-XX:+UseParallelOldGC打开。
使用-XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理数量相等。
此收集器可以进行如下配置:
最大垃圾回收暂停:指定垃圾回收时的最大暂停时间,通过-XX:MaxGCPauseMillis=指定。为毫秒数,如果指定了这个值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。此值可能会减少应用的吞吐量。吞吐量:吞吐量为垃圾回收时间与非垃圾回收时间的比值。通过-XX:GCTimeRatio=来设定,公式为1/(1+N),例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收,默认情况为99,既1%的时间用于垃圾回收。
并发收集器
可以保证大部分工作都并发执行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中,大型应用,使用-XX:+UseConcMarkSweepGC打开。
并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象,在每个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次,第二次暂停会比第一次长,在此过程中多个线程同时进行垃圾回收工作。

并发收集器使用处理器换来短暂的停顿时间,在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,一般情况下1<=K<=N/4

在只有一个处理器的主机上使用并发收集器,设置为incremental mode模式也可以获得较短的停顿时间。

浮动垃圾:由于在应用运行的同时进行垃圾回收,所以有些垃圾回收可能在垃圾回收完成时产生,这样就造成了“floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉,所以并发收集器一般需要20%的预留空间用于这些浮动垃圾。

Concurrent Mode Failure:并发收集器在应用程序运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先充满了,这种情况下将会发生“并发模式失败”,此时整个应用将会暂停。进行垃圾回收。

启动并发收集器:因为并发收集在应用运行时进行收集,睡衣必须保证收集完成之前有足够的内存空间供程序使用,否则会出现“Concurrent Mode Failure”,通过设置-XX:CMSinitiatingOccupancyFraction=指定还有多少剩余堆时开始执行并发收集。

并行:多个事件同一时间发生,同时做多件事
并发:多个事件在同一个时间间隔内发生。每年11.11日狂欢节,一天内接受的最大人数。

四、 垃圾回收器

收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。
4.1Serial 收集器

这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。
分代收集算法
特点:Stop the world

4.2 ParNew 收集器

分代收集算法
可以认为是 Serial 收集器的多线程版本

4.3 Parallel Scavenge 收集器

这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。
作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

4.4 Serial Old 收集器

收集器的老年代版本,单线程,使用 标记 —— 整理。

4.5 Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 —— 整理

4.6 CMS 收集器

CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除 算法实现。

运作步骤:

初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象
并发标记(CMS concurrent mark):进行 GC Roots Tracing
重新标记(CMS remark):修正并发标记期间的变动部分
并发清除(CMS concurrent sweep)

缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当此收集中处理掉他们,只好留待下一次GC时再清理。这一部分就叫做浮动垃圾。

4.7 G1 收集器

面向服务端的垃圾回收器。

优点:并行与并发、分代收集、空间整合、可预测停顿。
运作步骤:
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)

垃圾收集器没有最好的,根据环境的不同搭配不同的垃圾收集器

注:什么时候进行GC呢

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC。

Minor GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Full GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

老年代(Tenured)被写满
持久代(Perm)被写满
System.gc()被显示调用
上一次GC之后Heap的各域分配策略动态变化

五、内存分配规则

5.1 对象优先在Eden分配
新生代 GC (Minor GC)

发生在新生代的垃圾回收动作,频繁,速度快。

老年代 GC (Major GC / Full GC)

发生在老年代的垃圾回收动作,出现了 Major GC 经常会伴随至少一次 Minor GC(非绝对)。Major GC 的速度一般会比 Minor GC 慢十倍以上。

5.2 大对象直接金进入老年代

虚拟机提供一个参数-XX:PretenureSize Threshold 设置大于此值的算是大对象

5.3 长期存活的对象将进入老年代

一个对象每经过一次Minor GC 年龄增加一岁,当年龄达到一定程度(默认15),就晋升到老年代

5.4 动态对象年龄判定

在Survivor空间中相同年龄所有对象的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到最大值所需年龄

5.5 空间分配担保

取之前每一次回收晋升到老年代对象容量的平均大小值,与老年代的剩余空间进行比较,决定是否进行Full GC 来让老年代腾出更多空间。

六、类与类加载器

6.1 类加载器分类
从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。
JDK 默认提供了如下几种ClassLoader:
1.bootstrap class loader(引导类加载器)
①用于加载 Java的核心类
②底层由C++编写,并不继承java.lang.ClassLoader
③负责加载 JAVA_HOME中jre/lib/rt.jar | resources.jar…里所有的class
2.extensions class loader(扩展类加载器)
①负责加载Java的扩展类库
②默认加载$JAVA_HOME中jre/lib/ext/目下的所有jar
③父类加载器为null
3.system class loader(系统类加载器)(应用程序类加载器)
①根据Java应用的CLASSPATH来加载Java类
②Java中的应用类都是通过它来加载的
③父类加载器ExtClassLoader
4.custom class loader(自定义加载器)
6.2 双亲委托工作流程描述:

  1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
    每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
  2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
  3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
    ①CustomClassLoader接收到类加载请求
    查找缓存中是否有加载过该class实例:
    1>若缓存中已经存在,则返回该class实例
    2>若缓存中不存在,则将请求委派给父类加载器AppClassLoader
    ②AppClassLoader接收到类加载请求
    查找缓存中是否有加载过该class实例:
    1>若缓存中已经存在,则返回该class实例
    2>若缓存中不存在,则将请求委派给父类加载器ExtensionClassLoader
    ③ExtensionClassLoader接收到类加载请求
    查找缓存中是否有加载过该class实例:
    1>若缓存中已经存在,则返回该class实例
    2>若缓存中不存在,则将请求委派给父类加载器BootstrapClassLoader
    ⑤BootstrapClassLoader接收到类加载请求
    查找缓存中是否有加载过该class实例:
    1>若缓存中已经存在,则返回该class实例
    2>若缓存中不存在,BootstrapClassLoader在路径sun.mic.boot.class下搜索
    3>若在搜索范围内有搜索到,就将搜索到的class实例返回,否则交由子类加载器ExtensionClassLoader处理
    ⑥ExtensionClassLoader在路径java.ext.dirs下搜索
    若在搜索范围内有搜索到,就将搜索到的class实例返回,否则交由子类加载器AppClassLoader处理
    ⑦AppClassLoader在路径java.class.path下搜索
    若在搜索范围内有搜索到,就将搜索到的class实例返回,否则交由子类加载器CustomClassLoader处理
    ⑧CustomClassLoader在自定义时指定的路径下搜索该class,若搜索到便返回class实例.
    否则将抛出ClassNotFountException异常

问题思考:

(1)为什么要是用这种双亲模式呢?
①其主要目的就是避免相同的类被重复加载进入内存中而造成不必要的空间浪费.父类加载器如果已经加载过该类,那么子类就没有必要在对其进行加载.
②安全因素方面的考量,双亲模式时从下至上依次去找寻该类的迹象,如果不使用双亲模式,那么所有的类都会在子类加载器中被加载.这里的子类加载器也包括自定义类加载器.试想一个场景,在未使用双亲模式的情况下,随便一个人定义了一个包含病毒的自定义String类动态的替换掉了原有api中定义的String类型,那么这个自定义的String类一旦被加载就会出现隐患问题.相反,如果使用双亲模式的话,即便你自定义了携带病毒的String类也不会被加载(因为String类会在更高层级的父类加载器中被加载到,*具体就是因为针对java.开头的类,jvm的实现中已经保证了必须由bootstrp来加载
(2)JVM是如何判断2个class文件是否相同的呢?
①判断两个class文件相同有两个条件:
1>两个类的类名相同
2>由同一个类加载器加载
②一个test.java文件经过javac编译后生成test.class文件.有两个类加载器ClassLoaderA&ClassLoaderB分别对其进行加载分别生成对应的java.lang.Class对象,这两个对象站在JVM的角度会被认为是两个不同的对象,但它们却是同一分字节码文件.如果将这两个Class实例生成的具体对象进行转换时,就会抛出java.lang.ClassCaseException,提示这是两个不同的类型
6.3不遵循“双亲委托机制”的场景
上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。
Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。另外为了实现更灵活的类加载器OSGI以及一些Java appserver也打破了双亲委托机制。

设计模式

单例模式 确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例
多种单例模式实现
中介者模式:
通过让对象彼此解耦,增加对象的复用性
通过将控制逻辑集中,可以简化系统维护
通过中介者使一对所变成了一对一,便于理解
观察者一般可以看做是第三者,比如在学校上自习的时候,大家肯定都有过交头接耳、各种玩耍的经历,这时总会有一个“放风”的小伙伴,当老师即将出现时及时“通知”大家老师来了。再比如,拍卖会的时候,大家相互叫价,拍卖师会观察最高标价,然后通知给其它竞价者竞价,这就是一个观察者模式。定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
1、常见的设计模式
单例模式、工厂模式、建造模式、观察者模式、适配器模式、代理模式、装饰模式.
参考:https://www.cnblogs.com/cr330326/p/5627658.html
2、设计模式的六大原则及其含义

  1. 单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。主要作用实现代码高内聚,低耦合。
  2. 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
  3. 里氏替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。里氏替换原则是实现开闭原则的方式之一
  4. 依赖倒置原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
  5. 接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
  6. 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。
    参考:https://www.cnblogs.com/dolphin0520/p/3919839.html
    3、常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单利模式
    常见模式及优缺点:
    • 饿汉式:
    o 优点:不用加锁可以确保对象的唯一性,线程安全。
    o 缺点:初始化对象会浪费不必要的资源,未实现延迟加载。
    • 懒汉式:
    o 优点:实现了延时加载。
    o 缺点:线程不安全,想实现线程安全,得加锁(synchronized),这样会浪费一些不必要的资源。
    • 双重检测锁式(Double Check Lock–DCL):
    o 优点:资源利用率高,效率高。
    o 缺点:第一次加载稍慢,由于java处理器允许乱序执行,偶尔会失败。
    • 静态内部式:
    o 优点:第一次调用方法时才加载类,不仅保证线程安全还能保证对象的唯一,还延迟了单例的实例化
    o 缺点:
    4、设计模式在实际场景的应用
    单例:连接数据库,记录日志
    5、Spring中用到了哪些设计模式
  7. 工厂模式:spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
  8. 代理模式:Spring的AOP就是代理模式的体现。
  9. 观察者模式:常用的地方是Listener的实现,spring中ApplicationListener就是观察者的体现。
  10. 策略模式:spring在实例化对象的时候使用到了。
  11. 工厂方法:Spring中的FactoryBean就是典型的工厂方法模式。
    参考:https://www.cnblogs.com/hwaggLee/p/4510687.html
    6、MyBatis中用到了哪些设计模式
  12. Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
  13. 工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  14. 单例模式,例如ErrorContext和LogFactory;
  15. 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
  16. 组合模式,例如SqlNode和各个子类ChooseSqlNode等;
  17. 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
  18. 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
  19. 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
  20. 迭代器模式,例如迭代器模式PropertyTokenizer;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值