熬夜两天盘点2021年Java岗面试被问得最多的100道题

这么久没更新,为了体现我的诚意,在加班两个月搞完项目之后我马不停蹄找了很多在大厂做面试官的“狐朋狗友”要了今年秋招的数据,通宵肝了两天给大伙总结了2021年度Java岗面试被问得最多的100道题(我都这么说了,不点个赞说得过去吗)希望对正在备战春招的同学有所帮助。
之前一道题一道答案的风格被很多粉丝吐槽说没留思考的时间,所以这次我们先上题,思考的时候给你留足,做不出的题可以往下翻一翻答案。
AppClassLoader的⽗加载器是ExtClassLoader,ExtClassLoader的⽗加载器是BootstrapClassLoader。

JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会 先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到, 那么则会由AppClassLoader来加载这个类。

所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰进⾏加载。

JVM中哪些是线程共享区

堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的

你们项⽬如何排查JVM问题

对于还在正常运⾏的系统:

  1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况
  2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
  3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏ 调优了
  4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
  5. ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表 示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年轻 代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明修改有效
  6. 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些对象的创建,从⽽节省内存

对于已经发⽣了OOM的系统:

  1. ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(- XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base
  2. 我们可以利⽤jsisualvm等⼯具来分析dump⽂件
  3. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
  4. 然后再进⾏详细的分析和调试

总之,调优不是⼀蹴⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

⼀个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  1. ⾸先把字节码⽂件内容加载到⽅法区
  2. 然后再根据类信息在堆区创建对象
  3. 对象⾸先会分配在堆区中年轻代的Eden区,经过⼀次Minor GC后,对象如果存活,就会进⼊

Suvivor区。在后续的每次Minor GC中,如果对象⼀直存活,就会在Suvivor区来回拷⻉,每移动⼀次,年龄加1

  1. 当年龄超过15后,对象依然存活,对象就会进⼊⽼年代
  2. 如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉

怎么确定⼀个对象到底是不是垃圾?

  1. 引⽤计数算法: 这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。引⽤个数为0的就认为是垃圾。这是早期JDK中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。
  2. 可达性算法: 这种⽅式是在内存中,从根对象向下⼀直找引⽤,找到的对象就不是垃圾,没找到的对象就是垃圾。

JVM有哪些垃圾回收算法?

  1. 标记清除算法:
  • 标记阶段:把垃圾内存标记出来
  • 清除阶段:直接将垃圾内存回收。
  • 这种算法是⽐较简单的,但是有个很严重的问题,就是会产⽣⼤量的内存碎⽚。
  1. 复制算法:为了解决标记清除算法的内存碎⽚问题,就产⽣了复制算法。复制算法将内存分为⼤⼩ 相等的两半,每次只使⽤其中⼀半。垃圾回收时,将当前这⼀块的存活对象全部拷⻉到另⼀半,然后当前这⼀半内存就可以直接清除。这种算法没有内存碎⽚,但是他的问题就在于浪费空间。⽽ 且,他的效率跟存活对象的个数有关。
  2. 标记压缩算法:为了解决复制算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清 除算法是⼀样的,但是在完成标记之后,不是直接清理垃圾内存,⽽是将存活对象往⼀端移动,然后将边界以外的所有内存直接清除。

什么是STW?

STW: Stop-The-World,是在垃圾回收算法执⾏过程当中,需要将JVM内存冻结的⼀种状态。在STW 状态下,JAVA的所有线程都是停⽌执⾏的-GC线程除外,native⽅法可以执⾏,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

JVM参数有哪些?

JVM参数⼤致可以分为三类:

  1. 标注指令: -开头,这些是所有的HotSpot都⽀持的参数。可以⽤java -help 打印出来。
  2. ⾮标准指令: -X开头,这些指令通常是跟特定的HotSpot版本对应的。可以⽤java -X 打印出来。
  3. 不稳定参数: -XX 开头,这⼀类参数是跟特定HotSpot版本对应的,并且变化⾮常⼤。

说说对线程安全的理解

线程安全指的是,我们写的某段代码,在多个线程同时执⾏这段代码时,不会产⽣混乱,依然能够得到 正常的结果,⽐如i++,i初始化值为0,那么两个线程来同时执⾏这⾏代码,如果代码是线程安全的,那 么最终的结果应该就是⼀个线程的结果为1,⼀个线程的结果为2,如果出现了两个线程的结果都为1,则表示这段代码是线程不安全的。

所以线程安全,主要指的是⼀段代码在多个线程同时执⾏的情况下,能否得到正确的结果。

对守护线程的理解

线程分为⽤户线程和守护线程,⽤户线程就是普通线程,守护线程就是JVM的后台线程,⽐如垃圾回收 线程就是⼀个守护线程,守护线程会在其他普通线程都停⽌运⾏之后⾃动关闭。我们可以通过设置thread.setDaemon(true)来把⼀个线程设置为守护线程。

ThreadLocal的底层原理

  1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部, 该线程可以在任意时刻、任意⽅法中获取缓存的数据
  2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对 象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
  3. 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象
  4. ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)
熬夜两天盘点2021年Java岗面试被问得最多的100道题


并发、并⾏、串⾏之间的区别

  1. 串⾏:⼀个任务执⾏完,才能执⾏下⼀个任务
  2. 并⾏(Parallelism):两个任务同时执⾏
  3. 并发(Concurrency):两个任务整体看上去是同时执⾏,在底层,两个任务被拆成了很多份,然后

⼀个⼀个执⾏,站在更⾼的⻆度看来两个任务是同时在执⾏的

Java死锁如何避免?

造成死锁的⼏个原因:

  1. ⼀个资源每次只能被⼀个线程使⽤
  2. ⼀个线程在阻塞等待某个资源时,不释放已占有资源
  3. ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
  4. 若⼲线程形成头尾相接的循环等待资源关系

这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3 个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。

在开发过程中:

  1. 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁
  2. 要注意加锁时限,可以针对所设置⼀个超时时间
  3. 要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:

  1. 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊缓冲队列。
  3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。
  4. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
  5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌。这样,线程池可以动态的调整池中的线程数

线程池为什么是先添加列队⽽不是先创建最⼤线程?

当线程池中的核⼼线程都在忙时,如果继续往线程池中添加任务,那么任务会先放⼊队列,队列满了之 后,才会新开线程。这就相当于,⼀个公司本来有10个程序员,本来这10个程序员能正常的处理各种需求,但是随着公司的发展,需求在慢慢的增加,但是⼀开始这些需求只会增加在待开发列表中,然后这 10个程序员加班加点的从待开发列表中获取需求并进⾏处理,但是某⼀天待开发列表满了,公司发现现有的10个程序员是真的处理不过来了,所以就开始新招员⼯了。

ReentrantLock中的公平锁和⾮公平锁的底层实现

⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,它们的区别在于:线程在使

⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队, 则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。

不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。

另外,ReentrantLock是可重⼊锁,不管是公平锁还是⾮公平锁都是可重⼊的。

熬夜两天盘点2021年Java岗面试被问得最多的100道题


熬夜两天盘点2021年Java岗面试被问得最多的100道题


ReentrantLock中tryLock()和lock()⽅法的区别

  1. tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回true,没有加到则返回false
  2. lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值

CountDownLatch和Semaphore的区别和底层原理

CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,⼀个线程调⽤CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。

对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中 排队的线程依次唤醒。

Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使⽤该信号量,通 过acquire()来获取许可,如果没有许可可⽤则线程阻塞,并通过AQS来排队,可以通过release()⽅法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第⼀个线程开始依次唤 醒,直到没有空闲许可。

Sychronized的偏向锁、轻量级锁、重量级锁

  1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了
  2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻 量级锁底层是通过⾃旋来实现的,并不会阻塞线程
  3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
  4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒 这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。

Sychronized和ReentrantLock的区别

  1. sychronized是⼀个关键字,ReentrantLock是⼀个类
  2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁
  3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁
  4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁
  5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态
  6. sychronized底层有⼀个锁升级的过程

谈谈你对AQS的理解,AQS如何实现可重⼊锁?

  1. AQS是⼀个JAVA线程同步的框架。是JDK中很多锁⼯具的核⼼实现框架。
  2. 在AQS中,维护了⼀个信号量state和⼀个线程组成的双向链表队列。其中,这个线程队列,就是⽤来给线程排队的,⽽state就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的。 在不同的场景下, 有不⽤的意义。
  3. 在可重⼊锁这个场景下,state就⽤来表示加锁的次数。0标识⽆锁,每加⼀次锁,state就加1。释放锁state就减1。

谈谈你对IOC的理解

通常,我们认为Spring有两⼤特性IoC和AOP,那到底该如何理解IoC呢?

对于很多初学者来说,IoC这个概念给⼈的感觉就是我好像会,但是我说不出来。

那么IoC到底是什么,接下来来说说我的理解,实际上这是⼀个⾮常⼤的问题,所以我们就把它拆细了来回答,IoC表示控制反转,那么:

  1. 什么是控制?控制了什么?
  2. 什么是反转?反转之前是谁控制的?反转之后是谁控制的?如何控制的?
  3. 为什么要反转?反转之前有什么问题?反转之后有什么好处? 这就是解决这⼀类⼤问题的思路,⼤⽽化⼩。

那么,我们先来解决第⼀个问题:什么是控制?控制了什么? 我们在⽤Spring的时候,我们需要做什么:

  1. 建⼀些类,⽐如UserService、OrderService
  2. ⽤⼀些注解,⽐如@Autowired

但是,我们也知道,当程序运⾏时,⽤的是具体的UserService对象、OrderService对象,那这些对象是什么时候创建的?谁创建的?包括对象⾥的属性是什么时候赋的值?谁赋的?所有这些都是我们程序 员做的,以为我们只是写了类⽽已,所有的这些都是Spring做的,它才是幕后⿊⼿。

这就是控制:

  1. 控制对象的创建
  2. 控制对象内属性的赋值

如果我们不⽤Spring,那我们得⾃⼰来做这两件事,反过来,我们⽤Spring,这两件事情就不⽤我们做了,我们要做的仅仅是定义类,以及定义哪些属性需要Spring来赋值(⽐如某个属性上加@Autowired),⽽这其实就是第⼆个问题的答案,这就是反转,表示⼀种对象控制权的转移。

那反转有什么⽤,为什么要反转?

如果我们⾃⼰来负责创建对象,⾃⼰来给对象中的属性赋值,会出现什么情况?

⽐如,现在有三个类:

  1. A类,A类⾥有⼀个属性C c;
  2. B类,B类⾥也有⼀个属性C c;
  3. C类

现在程序要运⾏,这三个类的对象都需要创建出来,并且相应的属性都需要有值,那么除开定义这三个类之外,我们还得写:

A a = new A(); B b = new B(); C c = new C();

a.c = c;

b.c = c;

这五⾏代码是不⽤Spring的情况下多出来的代码,⽽且,如果类在多⼀些,类中的属性在多⼀些,那相应的代码会更多,⽽且代码会更复杂。所以我们可以发现,我们⾃⼰来控制⽐交给Spring来控制,我们 的代码量以及代码复杂度是要⾼很多的,反⾔之,将对象交给Spring来控制,减轻了程序员的负担。

总结⼀下,IoC表示控制反转,表示如果⽤Spring,那么Spring会负责来创建对象,以及给对象内的属 性赋值,也就是如果⽤Spring,那么对象的控制权会转交给Spring。

单例Bean和单例模式

单例模式表示JVM中某个类的对象只会存在唯⼀⼀个。

⽽单例Bean并不表示JVM中只能存在唯⼀的某个类的Bean对象。

Spring事务传播机制

多个事务⽅法相互调⽤时,事务如何在这些⽅法间传播,⽅法A是⼀个事务的⽅法,⽅法A执⾏过程中调

⽤了⽅法B,那么⽅法B有⽆事务以及⽅法B对事务的要求不同都会对⽅法A的事务具体执⾏造成影响, 同时⽅法A的事务对⽅法B的事务执⾏也有影响,这种影响具体是什么就由两个⽅法所定义的事务传播类 型所决定。

  1. REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则⾃⼰新建⼀个事务,如果当前存在事务,则加⼊这个事务
  2. SUPPORTS:当前存在事务,则加⼊当前事务,如果当前没有事务,就以⾮事务⽅法执⾏
  3. MANDATORY:当前存在事务,则加⼊当前事务,如果当前事务不存在,则抛出异常。
  4. REQUIRES_NEW:创建⼀个新事务,如果存在当前事务,则挂起该事务。
  5. NOT_SUPPORTED:以⾮事务⽅式执⾏,如果当前存在事务,则挂起当前事务
  6. NEVER:不使⽤事务,如果当前事务存在,则抛出异常
  7. NESTED:如果当前事务存在,则在嵌套事务中执⾏,否则REQUIRED的操作⼀样(开启⼀个事 务)

Spring事务什么时候会失效?

spring事务的原理是AOP,进⾏了切⾯增强,那么失效的根本原因是这个AOP不起作⽤了!常⻅情况有如下⼏种

1、发⽣⾃调⽤,类⾥⾯使⽤this调⽤本类的⽅法(this通常省略),此时这个this对象不是代理类,⽽是UserService对象本身!

解决⽅法很简单,让那个this变成UserService的代理类即可!

2、⽅法不是public的:@Transactional 只能⽤于 public 的⽅法上,否则事务不会失效,如果要⽤在

⾮ public ⽅法上,可以开启 AspectJ 代理模式。3、数据库不⽀持事务

4、没有被spring管理

5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

Spring中的Bean创建的⽣命周期有哪些步骤

Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:

  1. 推断构造⽅法
  2. 实例化
  3. 填充属性,也就是依赖注⼊
  4. 处理Aware回调
  5. 初始化前,处理@PostConstruct注解
  6. 初始化,处理InitializingBean接⼝
  7. 初始化后,进⾏AOP

当然其实真正的步骤更加细致,可以看下⾯的流程图

熬夜两天盘点2021年Java岗面试被问得最多的100道题


Spring中Bean是线程安全的吗

Spring本身并没有针对Bean做线程安全的处理,所以:

  1. 如果Bean是⽆状态的,那么Bean则是线程安全的
  2. 如果Bean是有状态的,那么Bean则不是线程安全的

另外,Bean是不是线程安全,跟Bean的作⽤域没有关系,Bean的作⽤域只是表示Bean的⽣命周期范围,对于任何⽣命周期的Bean都是⼀个对象,这个对象是不是线程安全的,还是得看这个Bean对象本身。

ApplicationContext和BeanFactory有什么区别

BeanFactory是Spring中⾮常核⼼的组件,表示Bean⼯⼚,可以⽣成Bean,维护Bean,⽽ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,也是⼀个Bean⼯⼚,但是ApplicationContext除开继承了BeanFactory之外,还继承了诸如EnvironmentCapable、MessageSource、ApplicationEventPublisher等接⼝,从⽽ApplicationContext还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的

Spring中的事务是如何实现的

  1. Spring事务底层是基于数据库事务和AOP机制的
熬夜两天盘点2021年Java岗面试被问得最多的100道题


  • ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean
  • 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解
  • 如果加了,那么则利⽤事务管理器创建⼀个数据库连接
  • 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重要的⼀步
  • 然后执⾏当前⽅法,⽅法中会执⾏sql
  • 执⾏完当前⽅法后,如果没有出现异常就直接提交事务
  • 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
  • Spring事务的隔离级别对应的就是数据库的隔离级别
  • Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的
  • Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql

Spring中什么时候@Transactional会失效

因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效,所以如果是被代理对象来调⽤这个⽅法,那么@Transactional是不会失效的。

同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效

Spring容器启动流程是怎样的

  • 在创建Spring容器,也就是启动Spring时:
  • ⾸先会进⾏扫描,扫描得到所有的BeanDefinition对象,并存在⼀个Map中
  • 然后筛选出⾮懒加载的单例BeanDefinition进⾏创建Bean,对于多例Bean不需要在启动过程中去进⾏创建,对于多例Bean会在每次获取Bean时利⽤BeanDefinition去创建
熬夜两天盘点2021年Java岗面试被问得最多的100道题


  • 利⽤BeanDefinition创建Bean就是Bean的创建⽣命周期,这期间包括了合并BeanDefinition、推断构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始化 后这⼀步骤中
  • 单例Bean创建完了之后,Spring会发布⼀个容器启动事件
  • Spring启动结束
  • 在源码中会更复杂,⽐如源码中会提供⼀些模板⽅法,让⼦类来实现,⽐如源码中还涉及到⼀些BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过BenaFactoryPostProcessor来实现的,依赖注⼊就是通过BeanPostProcessor来实现的
  • 在Spring启动过程中还会去处理@Import等注解

Spring⽤到了哪些设计模式

熬夜两天盘点2021年Java岗面试被问得最多的100道题


Spring Boot中常⽤注解及其底层实现

  • @SpringBootApplication注解:这个注解标识了⼀个SpringBoot⼯程,它实际上是另外三个注解的组合,这三个注解是:
  • @SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个配置类
  • @EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean
  • @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫描的路径是启动类所在的当前⽬录
  • @Bean注解:⽤来定义Bean,类似于XML中的<bean>标签,Spring在启动时,会对加了@Bean注 解的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象
  • @Controller、@Service、@ResponseBody、@Autowired都可以说

Spring Boot是如何启动Tomcat的

  1. ⾸先,SpringBoot在启动时会先创建⼀个Spring容器
  2. 在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean
  3. Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后启动Tomcat

Mybatis的优缺点

优点:

  1. 基于 SQL 语句编程,相当灵活,不会对应⽤程序或者数据库的现有设计造成任何影响,SQL 写在XML ⾥,解除 sql 与程序代码的耦合,便于统⼀管理;提供 XML 标签, ⽀持编写动态 SQL 语句, 并可重⽤。
  2. 与 JDBC 相⽐,减少了 50%以上的代码量,消除了 JDBC ⼤量冗余的代码,不需要⼿动开关连接;
  3. 很好的与各种数据库兼容( 因为 MyBatis 使⽤ JDBC 来连接数据库,所以只要JDBC ⽀持的数据库 MyBatis 都⽀持)。
  4. 能够与 Spring 很好的集成;
  5. 提供映射标签, ⽀持对象与数据库的 ORM 字段关系映射; 提供对象关系映射标签, ⽀持对象关系组件维护。

缺点:

  1. SQL 语句的编写⼯作量较⼤, 尤其当字段多、关联表多时, 对开发⼈员编写SQL 语句的功底有⼀定要求。
  2. SQL 语句依赖于数据库, 导致数据库移植性差, 不能随意更换数据库。

#{}和${}的区别是什么?

#{}是预编译处理、是占位符, ${}是字符串替换、是拼接符。

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调⽤ PreparedStatement 来赋值; Mybatis在处理${}时,会将sql中的${}替换成变量的值,调⽤ Statement 来赋值;

使⽤#{}可以有效的防⽌ SQL 注⼊, 提⾼系统安全性。

索引的基本原理

索引⽤来快速地寻找那些具有特定值的记录。如果没有索引,⼀般来说执⾏查询时遍历整张表。 索引的原理:就是把⽆序的数据变成有序的查询

  1. 把创建了索引的列的内容进⾏排序
  2. 对排序结果⽣成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从⽽拿到具体数据
#Java工程师##秋招##面试题目##Java##面试复盘#
全部评论
点赞 回复 分享
发布于 2022-08-12 10:32
楼主后面还会更新吗
点赞 回复 分享
发布于 2022-08-05 11:05

相关推荐

牛客383479252号:9,2学生暑期实习失利开始投小厂,给这群人整自信了
点赞 评论 收藏
分享
评论
11
82
分享

创作者周榜

更多
牛客网
牛客企业服务