1、简述一下JVM的内存模型
1.JVM内存模型简介
JVM定义了不同运行时数据区,他们是用来执行应用程序的。某些区域随着JVM启动及销毁,另外一 些区域的数据是线程性独立的,随着线程创建和销毁。jvm内存模型总体架构图如下:
JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。下图是根据自己理解画的一个 JVM内存模型架构图:
2、说说堆和栈的区别
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在 区域不连续,会有碎片。
1、功能不同
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变 量,还是类变量,它们指向的对象都存储在堆内存中。
2、共享性不同
栈内存是线程私有的。 堆内存是所有线程共有的。
3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。 栈空间不足:java.lang.StackOverFlowError。 堆空间 不足:java.lang.OutOfMemoryError。
4、空间大小
栈的空间大小远远小于堆的。
3、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言” ?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字 节码文件 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
4、如何判断对象可以被回收?
判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计 数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引 用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对 象。
5、说一下 JVM 有哪些垃圾回收器?
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了 7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、 PraNew、 Parallel、Scavenge,回收老年代的收集器包括Serial Old、 Parallel Old、CMS,还有用于回收整个Java堆的 G1收集器。不同收集器之间的连线表示它们可以搭配使用。
JVM 中有以下几种常见的垃圾回收器:
Serial 收集器
- 它是一个单线程的收集器,只使用一个线程去执行垃圾回收任务。在垃圾回收期间,所有用户线程都会被暂停(Stop - The - World,STW)。例如,在新生代回收过程中,它会逐个检查新生代中的对象,判断对象是否还存活。如果对象不再被任何引用链所连接,就将其标记为可回收对象,然后进行回收。
- 主要采用标记 - 复制算法。新生代被划分为 Eden 区和两个 Survivor 区(From 和 To)。在垃圾回收时,将 Eden 区和 From Survivor 区中存活的对象复制到 To Survivor 区,然后清理 Eden 区和 From Survivor 区。之后交换 From 和 To Survivor 区的角色。
ParNew 收集器
- 它是 Serial 收集器的多线程版本。在新生代回收时,可以启动多个线程同时进行垃圾回收。它也是基于标记 - 复制算法。与 Serial 收集器类似,它会先标记新生代中的存活对象,然后将存活对象复制到 Survivor 区。不过,由于多线程的特性,在复制对象的过程中,多个线程可以并行地从 Eden 区和 From Survivor 区复制对象到 To Survivor 区,从而提高回收效率。
Parallel Scavenge 收集器
- 和 ParNew 收集器一样,它也是多线程的新生代收集器。不过,它的目标是达到一个可控制的吞吐量。它采用标记 - 复制算法。在垃圾回收过程中,它会根据系统的配置(如 -XX:GCTimeRatio 参数设置垃圾回收时间占总运行时间的比例)来调整新生代的大小和垃圾回收的频率。例如,如果设置 -XX:GCTimeRatio=9,意味着垃圾回收时间占总运行时间的 1/10,它会通过调整新生代的大小来尽量满足这个比例要求。
Serial Old 收集器
1. **工作原理**
- 它是 Serial 收集器的老年代版本。它是单线程的,采用标记 - 整理算法。在垃圾回收时,先标记老年代中的存活对象,然后将存活对象向内存的一端进行整理,清理掉中间的空白区域。这种方式可以避免内存碎片化的问题,因为老年代的对象相对比较稳定,不会频繁地创建和销毁。
2. **适用场景**
- 通常和 Serial 收集器或者 ParNew 收集器配合使用。当新生代的对象晋升到老年代后,可以使用 Serial Old 收集器来回收老年代。它适用于对老年代垃圾回收要求不高的场景,比如一些小型应用或者单核处理器的系统,因为单线程的特性,它的资源消耗相对较小。
Parallel Old 收集器
- 它是 Parallel Scavenge 收集器的老年代版本,采用多线程的标记 - 整理算法。多个线程可以并行地标记老年代中的存活对象,然后将存活对象整理到内存的一端。这种并行的方式可以提高老年代垃圾回收的效率,减少垃圾回收时的停顿时间。
G1(Garbage - First)收集器
- 适合大内存、多核处理器的系统。例如,在一些大型的云计算平台或者大数据处理平台中,堆内存可能非常大,G1 收集器可以高效地管理内存,通过分区域回收的方式,减少垃圾回收的停顿时间,同时避免内存碎片化问题。
6、什么情况下会内存溢出?
堆内存溢出:(1)当对象一直创建而不被回收时(2)加载的类越来越多时(3)虚拟机栈的线程越来越多时
栈溢出:方法调用次数过多,一般是递归不当造成
7、对象的创建过程
(1)检查类是否已被加载,没有加载就先加载类
(2)为对象在堆中分配内存,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。
(3)初始化,将对象中的属性都分配0值或null
(4)设置对象头
(5)为属性赋值和执行构造方法
8、说说什么是垃圾回收GC?
GC 是 Garbage Collection 的缩写,即垃圾回收。它是 Java 虚拟机(JVM)自动管理内存的一种机制。在 Java 程序运行过程中,会不断创建对象,当这些对象不再被使用时,如果不进行回收,就会占用内存空间,导致内存泄漏等问题。GC 的作用就是自动识别并回收这些不再使用的对象所占用的内存,使程序能够高效地运行,而无需程序员手动管理内存。
9、GC如何判断对象可以被回收?
(1)引用计数法:已淘汰,为每个对象添加引用计数器,引用为0时判定可以回收,会有两个对象相互引用无法回收的问题
(2)可达性分析法:从GCRoot开始往下搜索,搜索过的路径称为引用链,若一个对象GCRoot没有任何的引用链,则判定可以回收
GCRoot有:虚拟机栈中引用的对象,方法区中静态变量引用的对象,本地方法栈中引用的对象
三、Java多线程篇
1、说说什么是Java多线程
在 Java 中,线程是程序执行的最小单位。它可以执行一个任务或者一系列任务。线程是 CPU 调度的基本单位,多个线程可以并发执行,从而提高程序的执行效率。例如,在一个文本编辑器程序中,可以有一个线程专门用于处理用户的输入,另一个线程用于自动保存文档,这样可以让用户在输入文字的同时,程序后台自动保存文档,提高用户体验。
2、说说线程与进程的区别
进程:
进程是操作系统进行资源分配和调度的一个独立单位。它包含了程序运行所需的所有资源,如内存空间、文件描述符等。一个 Java 应用程序运行时就是一个进程,比如启动一个 Java Web 服务,它就是一个独立的进程。
线程:
线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享所属进程的资源,如内存空间。在 Java 中,线程的创建和管理是由 Java 虚拟机(JVM)和操作系统共同完成的。例如,在一个 Java Web 服务进程中,可以有多个线程处理不同的 HTTP 请求,这些线程共享进程的内存空间,包括类加载器加载的类信息、静态变量等。
3、Java 中创建线程的方式
(一)继承 Thread 类
创建一个继承自 Thread 类的子类。重写 run() 方法,在 run() 方法中编写线程要执行的任务代码。创建 Thread 子类的实例对象。调用 start() 方法启动线程。
(二)实现 Runnable 接口
创建一个实现了 Runnable 接口的类。实现 run() 方法,在 run() 方法中编写线程要执行的任务代码。创建 Runnable 实现类的实例对象。创建 Thread 类的实例对象,并将 Runnable 实现类的实例对象作为参数传递给 Thread 类的构造方法。调用 Thread 类实例对象的 start() 方法启动线程。
(三)使用 Callable 和 Future
创建一个实现了 Callable 接口的类。实现 call() 方法,在 call() 方法中编写线程要执行的任务代码,并且可以返回一个结果。创建 Callable 实现类的实例对象。使用 Executors 工具类创建一个 ExecutorService 线程池。调用 ExecutorService 的 submit() 方法提交 Callable 任务,该方法会返回一个 Future 对象。通过 Future 对象的 get() 方法获取线程执行的结果。
4、什么是线程上下文切换
当一个线程被剥夺cpu使用权时,切换到另外一个线程执行
5、什么是死锁
死锁指多个线程在执行过程中,因争夺资源造成的一种相互等待的僵局
6、什么是AQS锁?
AQS是一个抽象类,可以用来构造锁和同步类,如ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。
AQS的原理是,AQS内部有三个核心组件,一个是state代表加锁状态初始值为0,一个是获取到锁的线程,还有一个阻塞队列。当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的
可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。
7、有哪些常见的AQS锁
AQS(AbstractQueuedSynchronizer)是 Java 并发包 `java.util.concurrent.locks` 中的一个核心类,用于构建锁和其他同步组件。AQS 提供了一种基于 FIFO 等待队列的机制,用于管理线程的获取和释放资源的顺序,并提供了一些模板方法供子类实现具体的同步策略。以下是一些常见的基于 AQS 实现的锁:
ReentrantLock(可重入锁)
允许同一个线程多次获取锁,每次获取锁时计数器加一,每次释放锁时计数器减一,直到计数器为 0 时才完全释放锁。支持公平锁(按照请求顺序分配锁)和非公平锁(允许插队)。
使用场景:替代 `synchronized` 关键字,提供更灵活的锁定机制。
ReentrantReadWriteLock(可重入读写锁)
将读操作与写操作分离,允许多个读操作并发执行,但写操作是排他的,即只有一个线程可以进行写操作,并且写操作会阻塞所有其他读写操作。
使用场景:适用于读多写少的情况,提高并发性能。
Semaphore(信号量)
用于控制同时访问某个资源的线程数量。它可以用来限制同时执行的线程数,或者实现资源池的管理。
使用场景:限制资源的并发访问,例如数据库连接池、线程池等。
CountDownLatch
允许一个或多个线程等待其他线程完成一组操作后再继续执行。初始化时设定一个计数值,每个事件完成后调用 `countDown()` 方法使计数递减,当计数达到零时,所有因调用 `await()` 方法而等待的线程被释放。
使用场景:确保某些操作在继续之前完成,比如等待所有子线程完成任务后主线程再继续。
CyclicBarrier(循环屏障)
允许一组线程相互等待,直到所有线程都达到某个公共屏障点后再继续执行。它常用于分阶段的任务并行执行。
使用场景:分阶段的任务并行执行,例如多线程计算任务的每个阶段都需要所有线程完成后再进入下一个阶段。
8、Thread类中的yield方法有什么作用?
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法 而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可 能在进入到暂停状态后马上又被执行。
9、Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务, execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了 Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些 方法。
10、说一说自己对于 synchronized 关键字的了解
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一 个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优 化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
11、sleep()和wait()的区别
(1)wait()是Object的方法,sleep()是Thread类的方法
(2)wait()会释放锁,sleep()不会释放锁
(3)wait()要在同步方法或者同步代码块中执行,sleep()没有限制
(4)wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒
12、volatile关键字的作用?
volatile 是 Java 中的一个关键字,用于修饰变量。它主要解决多线程环境下的可见性和禁止指令重排序的问题。
volatile关键字保证变量的可见性和有序性,不保证原子性。使用了 volatile 修饰变量后,在变量修改后会立即同步到主存中,每次用这个变量前会从主存刷新。
13、什么是CAS锁
CAS锁可以保证原子性,思想是更新内存时会判断内存值是否被别人修改过,如果没有就直接更新。如果被修改,就重新获取值,直到更新完成为止。这样的缺点是:
(1)只能支持一个变量的原子操作,不能保证整个代码块的原子操作
(2)CAS频繁失败导致CPU开销大
(3)ABS问题:线程1和线程2同时去修改一个变量,将值从A改为B,但线程1突然阻塞,此时线程2将A改为B,然后线程3又将B改成A,此时线程1将A又改为B,这个过程线程2是不知道的,这就是ABA问题,可以通过版本号或时间戳解决
14、说说ThreadLocal原理?
ThreadLocal
是 Java 中的一个类,用于创建线程局部变量。每个线程可以访问自己内部的变量副本,而不会与其他线程的变量副本发生冲突。这在多线程编程中非常有用,可以避免线程之间的数据共享和同步问题。
使用场景
- 避免共享资源的同步问题:在多线程环境中,共享资源的同步是一个常见的问题。使用
ThreadLocal
可以为每个线程提供独立的变量副本,从而避免了线程之间的竞争条件。 - 管理线程上下文:在一些场景中,需要在多个方法调用之间传递线程上下文信息,例如事务管理、用户身份信息等。
ThreadLocal
可以方便地实现这一点,而不需要在每个方法参数中传递这些信息。
15、多线程有什么用?
一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这 个回答更扯淡。所谓"知其然知其所以然" ,"会用"只是"知其然" ,"为什么用"才是"知其所以然" ,只 有达到"知其然知其所以然"的程度才可以说是把一个知识点运用自如。 OK,下面说说我对这个问题 的看法:
(1)发挥多核CPU的优势
随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、 8核甚至 16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费 了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过 线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用 CPU的目的。
(2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运 行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程, 就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取 某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
(3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A ,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务 D ,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
四、Mysql篇
1、数据库的三范式是什么
第一范式:列不可再分
第二范式:行可以唯一区分,主键约束
第三范式:表的非主属性不能依赖与 其他表的非主属性外键约束且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。
2、什么是事务?
事务是一系列操作,这些操作要么全部成功,要么全部失败。事务确保数据库的完整性。
原子性:组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都成功, 整个事务才会提交。任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始 状态。
一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的。即数据不会被破坏。 如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。
隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干 扰
持久性:一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。
3、事务的隔离级别有哪些?
事务的隔离级别有以下几种:
READ UNCOMMITTED:读取未提交,允许读取未提交的事务数据,可能会出现脏读。
READ COMMITTED:读取已提交,只能读取已提交的事务数据,避免了脏读,但可能会出现不可重复读。
REPEATABLE READ:可重复读,确保在同一个事务中多次读取同一数据时,结果一致,避免了不可重复读,但可能会出现幻读。
SERIALIZABLE:可串行化,最高的隔离级别,避免了脏读、不可重复读和幻读,但性能开销最大。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
读未提交 READ-UNCOMMITTED | √ | √ | √ |
读提交 READ-COMMITTED | × | √ | √ |
可重复读 REPEATABLE-READ | × | × | √ |
串行化 SERIALIZABLE | × | × | × |
4、如何优化 SQL 查询?
索引:为经常查询的列创建索引,可以加快查询速度。
查询优化:避免使用 SELECT *
,只查询需要的列;使用 EXPLAIN
分析查询计划,优化复杂的查询。
分页查询:使用 LIMIT
和 OFFSET
进行分页查询,减少数据传输量。
避免子查询:尽量使用 JOIN
替代子查询,提高查询效率。
5、如何优化数据库表结构?
范式化:遵循数据库范式,减少数据冗余,提高数据一致性。
反范式化:在某些情况下,适当的数据冗余可以提高查询性能,但需要权衡。
分区:对大表进行分区,可以提高查询和维护效率。
6、如何优化数据库连接?
连接池:使用连接池(如 HikariCP、DBCP、C3P0)管理数据库连接,减少连接的创建和销毁开销。
连接复用:合理配置连接池参数,确保连接的复用。
7、如何处理数据库的高并发问题?
读写分离:将读操作和写操作分离到不同的数据库实例,提高读写性能。
缓存:使用缓存(如 Redis)减少数据库的读取压力。
分库分表:将数据分散到多个数据库或表中,提高并发处理能力。
消息队列:使用消息队列(如 Kafka)异步处理数据,减少数据库的写入压力。
8、 SQL优化手段有哪些
1、查询语句中不要使用select *
2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
3、减少使用IN或者NOT IN ,使用exists ,not exists或者关联查询语句替代
4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时, union all会更好)
5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表 扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有 null值,然后这样查询: select id from t where num=0
9、简单说一说drop、 delete与truncate的区别
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别
delete和truncate只删除表的数据不删除表的结构 速度,一般来说: drop> truncate >delete delete 语句是dml,这个操作会放到rollback segement中,事务提交之后才生效; 如果有相应的trigger,执行 的时候将被触发. truncate,drop是ddl, 操作立即生效,原数据不放到rollback segment中,不能回滚. 操作不触发trigger。
10、什么是视图?
视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是 有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易, 相比多表查询。
11、 什么是内联接、左外联接、右外联接?
内联接(Inner Join):匹配2张表中相关联的记录。
左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记 录,右表中未匹配到的字段用NULL表示。
右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记 录,左表中未匹配到的字段用NULL表示。在判定左表和右表时,要根据表名出现在Outer Join 的左右位置关系。
12、大表如何优化?
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
1. 限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们 可以控制在一个月的范围内;
2. 读/写分离
经典的数据库拆分方案,主库负责写,从库负责读;
3. 垂直分区
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信 息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。
4. 水平分区
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达 到了分布式的目的。 水平拆分可以支撑非常大的数据量。
13、char和varchar的区别
char是不可变的,最大长度为255,varchar是可变的字符串,最大长度为2^16
五、SpringBoot篇
1、简单说说什么是 Spring Boot?
Spring Boot 是由 Pivotal 团队提供的基于 Spring 框架的全新框架,旨在简化 Spring 应用的初始搭建以及开发过程。它通过提供一系列默认配置来快速启动和运行 Spring 应用,同时允许开发者通过少量的配置来覆盖默认设置。
主要优点:
快速搭建:提供了大量自动配置,可以快速启动和运行 Spring 应用。
独立运行:内嵌Tomcat、Jetty 等容器,无需部署 WAR 文件。
简化配置:通过 application.properties
或 application.yml
文件简化配置管理。
自动配置:根据类路径中的库自动配置 Spring 应用。
生产就绪:提供生产就绪的功能,如性能指标、健康检查和外部化配置。
2、springboot自动配置原理
在spring—boot—autoconfigura包下存放了spring内置的自动配置类和spring.factories文件,这个文件中存放了这些配置类的全类名 ;
启动类@SpringbootApplication注解下,有三个关键注解
(1)@springbootConfiguration:表示启动类是一个自动配置类
(2)@CompontScan:扫描启动类所在包下及子包的组件到容器中
(3)@EnableConfigutarion,下面有个子注解@Import会导入上面所说的自动配置类,这些配置类会根据元注解的装配条件生效,生效的类就会被实例化,加载到ioc容器中;这些自动配置类还会通过xxxProperties文件里配置来进行属性设值。
3、springboot常用注解
@RestController :修饰类,该控制器会返回Json数据
@RequestMapping("/path") :修饰类,该控制器的请求路径
@Autowired : 修饰属性,按照类型进行依赖注入
@PathVariable : 修饰参数,将路径值映射到参数上
@ResponseBody :修饰方法,该方法会返回Json数据
@RequestBody(需要使用Post提交方式) :修饰参数,将Json数据封装到对应参数中
@Controller@Service@Compont: 将类注册到ioc容器
@Transaction:开启事务
4、Actuator 的作用是什么?常用的端点有哪些?
-
作用:提供生产级监控和管理功能。
-
常用端点:
-
/actuator/health
:应用健康状态。 -
/actuator/info
:应用自定义信息(需配置)。 -
/actuator/metrics
:JVM、系统指标。 -
/actuator/env
:环境变量和配置属性。
-
-
安全配置:通过
management.endpoints.web.exposure.include=*
暴露端点,结合 Spring Security 限制访问。
5、IOC是什么?
IOC是控制反转,是一种思想,把对象的创建和调用从程序员手中交由IOC容器管理,降低对象之间的依赖关系。
创建一个bean的方式有xml方式、@Bean注解方式、@Componte方式
我们在对一个bean进行实例化后,要对他的属性进行填充,大多数我们都是使用 @Autowire直接的填充依赖注入的,他是有限按照类型进行匹配。
6、Spring Boot 和 Spring MVC 有什么区别?
-
Spring MVC 是一个 Web 框架,需手动配置 DispatcherServlet、视图解析器等。
-
Spring Boot 是 Spring 的扩展,通过自动配置和内嵌服务器简化 Spring 应用的搭建。
六、Redis篇
1、简单说说什么是Redis
Redis(Remote Dictionary Server,远程字典服务)是一个开源的、高性能的键值存储数据库,通常用作数据库、缓存系统和消息传递系统。它支持多种数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)和范围查询、位图(bitmaps)、超日志(hyperloglogs)以及地理空间索引(geospatial indexes)等。
Redis 的数据存储在内存中,这使得它能够提供极高的读写速度,通常能达到每秒数十万次的读写操作。此外,Redis 还支持数据持久化,可以通过将内存中的数据定期写入磁盘来防止数据丢失。它还支持事务,可以将多个命令打包,然后一次性、顺序地执行。Redis 通常用于提高大型应用的性能,通过为数据库提供高速的缓存层,减少对磁盘数据库的直接访问次数。
2、介绍下Redis有哪些数据类型
常用基本数据类型如下:
string | 字符串(一个字符串类型最大存储容量为512M) |
list | 可以重复的集合 |
set | 不可以重复的集合 |
hash | 类似于Map<String,String> |
zset(sorted set) | 带分数的set |
3、Redis内存淘汰策略
当内存不足时按设定好的策略进行淘汰,策略有
Redis 提供 6 种内存淘汰策略(maxmemory-policy
配置):
-
noeviction
:拒绝写入新数据(默认)。 -
allkeys-lru
:对所有 key 使用 LRU 算法淘汰。 -
volatile-lru
:对设置了过期时间的 key 使用 LRU。 -
allkeys-random
:随机淘汰任意 key。 -
volatile-random
:随机淘汰有过期时间的 key。 -
volatile-ttl
:淘汰即将过期的 key。
4、Redis如何解决缓存击穿?
缓存击穿是值一个key非常热点,key在某一瞬间失效,导致大量请求到达数据库.
解决方案:
(1)设置热点数据永不过期
(2)给缓存重建的业务加上互斥锁,缺点是性能低
5、Redis如何解决缓存雪崩?
雪崩是:大量缓存同时失效,请求打到数据库。
解决方案:
(1)搭建集群保证高可用
(2)进行数据预热,给不同的key设置随机的过期时间
(3)给缓存业务添加限流降级,通过加锁或队列控制操作redis的线程数量
(4)给业务添加多级缓存
6、Redis提供了哪几种持久化方式?
(1)RDB持久化(快照):
每隔一段时间,将内存中的数据集写到磁盘,适合备份和恢复,但可能丢失最近数据。
(2)AOF 持久化(追加日志):
记录所有写操作命令,数据更安全,但文件较大且恢复速度慢。
(3)混合模式(Redis 4.0+):
结合 RDB 和 AOF,先用 RDB 恢复,再用 AOF 补全增量数据。
7、Redis 如何实现高可用?
-
主从复制:主节点写,从节点读,数据异步复制。
-
哨兵模式(Sentinel):监控主从节点,自动故障转移(主节点宕机时选举新主)。
-
Cluster 模式:分片存储(16384 个槽),每个节点负责一部分槽,支持自动故障转移。