8、 String、StringBuffer 和 StringBuilder 的区别是什么?
11、 Collection包结构,与Collections的区别
15、try catch finally ,try里有return ,finally还执行么?
19、 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
6、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言” ?
9、Java线程池中submit() 和 execute()方法有什么区别?
10、说一说自己对于 synchronized 关键字的了解
9、简单说一说drop、 delete与truncate的区别
6、Spring Boot 和 Spring MVC 有什么区别?
一、基础篇
1、面向对象和面向过程的区别
面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一 一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发.
面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤, 而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特 性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。
2、简单说说封装、继承与多态
封装:
-
概念:封装是面向对象编程中的一种基本思想。它将对象的数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的对象(类)。同时,封装可以隐藏对象的内部实现细节,只对外提供有限的接口供其他对象访问。
继承:
-
概念:继承是一种允许新类(子类)继承现有类(父类)的属性和方法的机制。子类可以继承父类的公共属性和方法,并且可以添加自己的新属性和方法,也可以重写父类的方法来实现特定的功能。
多态:
-
概念:多态是指允许不同的对象对同一消息做出响应,具体调用哪个对象的方法在运行时根据对象的实际类型动态确定。多态分为编译时多态(方法重载)和运行时多态(方法覆盖)。
-
举例:还是以动物类为例。动物类有一个叫的方法(
void call()
)。狗类和猫类都继承自动物类,并且都覆盖了叫的方法。狗类的叫方法是汪汪叫,猫类的叫方法是喵喵叫。当有一个动物类型的引用指向狗对象时,调用animal.call()
就会发出汪汪的叫声;当这个引用指向猫对象时,调用animal.call()
就会发出喵喵的叫声。这就是运行时多态,具体调用哪个类的叫方法是在运行时根据对象的实际类型(狗或猫)动态确定的。
3、标识符的命名规则
标识符的含义: 是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等 等,都是标识符。
命名规则:(硬性要求) 标识符可以包含英文字母,0-9的数字,$以及_ 标识符不能以数字开头 标 识符不是关键字
命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。 变量
名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。 方法名规范:同变量名。
4、Java自动装箱与拆箱
装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的 valueOf(int) 方法
拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的 intValue方法
5、 方法重载和方法重写的区别
重写(Override)
从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子 类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名, 参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
重载(Overload )
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
6、 equals与 == 的区别
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是 否是指相同一个对象。比较的是真正意义上的指针操作。
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所 以适用于所有对象,如果没有对该方法进行重写的话,调用的仍然是Object类中的方法,而Object 中的equals方法返回的却是==的判断。
7、 Hashcode的作用
java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set 中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样 的方法就会比较满。
于是有人发明了哈希算法来提高集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。
hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
8、 String、StringBuffer 和 StringBuilder 的区别是什么?
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的 字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成 新的String对象。
StringBuilder 和 StringBuffer:都是可变的(mutable)。它们可以被多次修改,而不会创建新的对象.StringBuffer:是线程安全的。它提供了同步方法,确保在多线程环境下对字符串的修改是安全的。但是,这种同步机制会带来性能开销。StringBuilder:是线程不安全的。它没有提供同步方法,因此在多线程环境下使用时需要手动同步。由于没有同步开销,StringBuilder
在单线程环境下的性能优于StringBuffer
。
操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。
9、ArrayList和linkedList的区别
ArrayList:
基于动态数组实现。底层是一个数组,当数组满了之后,会自动扩容,通常扩容为原数组的 1.5 倍(具体扩容策略在不同版本的 JDK 中可能有所不同)。
ArrayList<Integer> list = new ArrayList<>();
list.add(1); // 底层会创建一个数组,并将元素添加到数组中
list.add(2);
LinkedList:
基于双向链表实现。每个元素是一个节点,每个节点包含数据部分和指向前后节点的指针。
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // 底层会创建一个节点,并将元素添加到节点中
list.add(2);
10、 HashMap和HashTable的区别
1、两者父类不同
HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时 实现了map、Cloneable(可复制)、 Serializable(可序列化)这三个接口。
2、对外提供的接口不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。 elments() 方法继承自 Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实 上,contansValue() 就只是调用了一下contains() 方法。
3、对null的支持不同
Hashtable:key和value都不能为null。
HashMap :key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个 key值对应的value为null。
4、安全性不同
HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自 己处理多线程的安全问题。
Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。
虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部 分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。
ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为 ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
5、初始容量大小和每次扩充容量大小不同
6、计算hash值的方法不同
11、 Collection包结构,与Collections的区别
Collection是集合类的上级接口,子接口有 Set、 List、 LinkedList、ArrayList、Vector、Stack、 Set; 是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的 Collection框架。
12、深拷贝和浅拷贝的区别是什么?
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指 向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向 被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的 对象都复制了一遍.
13、final有哪些用法?
final也是很多面试喜欢问的地方,但我觉得这个问题很无聊,通常能回答下以下5点就不错了:
被final修饰的类不可以被继承
被final修饰的方法不可以被重写
被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
被final修饰的方法,JVM会尝试将其内联,以提高运行效率
被final修饰的常量,在编译阶段会存入常量池中.
除此之外,编译器对final域要遵守的两个重排序规则更好:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序.
14、 static都有哪些用法?
所有的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/ 方法都属于类的静态资源,类实例所共享.
除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:
public calss PreCache{ static{ //执行相关操作 } } |
此外static也多用于修饰内部类,此时称之为静态内部类.
最后一种用法就是静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名。
15、try catch finally ,try里有return ,finally还执行么?
执行,并且finally的执行早于try里面的return
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的 值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数 返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
16、多态的作用
多态的实现要有继承、重写,父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系,增加类的可扩充性和灵活性。
多态允许你通过统一的接口来处理不同类型的对象,这样在添加新的类型时,不需要修改现有的代码,只需要实现相同的接口或继承相同的父类即可。这使得代码的扩展性大大增强。
17、什么是反射?
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象, 都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所 有信息。
I 这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
应用场景有:要操作权限不够的类属性和方法时、实现自定义注解时、动态加载第三方jar包时、按需加载类,节省编译和初始化时间;
获取class对象的方法有:class.forName(类路径),类.class(),对象的getClass()
18、Java创建对象得五种方式?
(1)new关键字 (2)Class.newInstance (3)Constructor.newInstance
(4)Clone方法 (5)反序列化
19、 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
线程与进程相似,但线程是一个比进程更小的执行单位。 一个进程在其执行的过程中可以产生多个 线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代 码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序 即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算 机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空 间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而 各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系 统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同 时执行一个以上的程序段。
20、用过 ArrayList 吗?说一下它有什么特点?
只要是搞 Java 的肯定都会回答“用过”。所以,回答题目的后半部分——ArrayList 的特点。可以从这 几个方面去回答:
Java 集合框架中的一种存放相同类型的元素数据,是一种变长的集合类,基于定长数组实现,当加入数据达到一定程度后,会实行自动扩容,即扩大数组大小。底层是使用数组实现,添加元素。
如果 add(o),添加到的是数组的尾部,如果要增加的数据量很大,应该使用ensureCapacity() 方法,该方法的作用是预先设置 ArrayList 的大小,这样可以大大提高初始化速度。
如果使用 add(int,o),添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩 容机制。
高并发的情况下,线程不安全。多个线程同时操作 ArrayList ,会引发不可预知的异常或错误。ArrayList 实现了 Cloneable 接口,标识着它可以被复制。注意:ArrayList 里面的 clone() 复制其实是浅复制。
21、有数组了为什么还要搞个 ArrayList 呢?
通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。
可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。
22、说说Hashtable 与 HashMap 的区别
本来不想这么写标题的,但是无奈,面试官都喜欢这么问 HashMap。
1. 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0 ,HashMap 出生于 JDK 1.2。
2. 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8 )。
3. HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。 Hashtable 继承
Dictionary。
4. Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的, HashMap 是非线程安全的。
5. Hashtable 的 key 不能为 null ,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方 法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之 前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。
6. HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果 key==null ,则其 hash=0 ;至于 value 是否为 null ,根本没有判断过。
7. Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出 来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时 间的,效率很低。 HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取 模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
8. Hashtable、 HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。
9. 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的 2n+1( n 为上一次的长度)而 HashMap 的初始长度为 16 ,之后每次扩充变为原来的两倍。
23、红黑树有哪几个特征?
紧接上个问题,面试官很有可能会问红黑树,下面把红黑树的几个特征列出来:
24、说说你平时是怎么处理 Java 异常的
try-catch-finally
try 块负责监控可能出现异常的代码
catch 块负责捕获可能出现的异常,并进行处理
finally 块负责清理各种资源,不管是否出现异常都会执行
其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程
二、JVM篇
1、知识点汇总
JVM是Java运行基础,面试时一定会遇到JVM的有关问题,内容相对集中,但对只是深度要求较高.
其中内存模型,类加载机制,GC是重点方面.性能调优部分更偏向应用,重点突出实践能力.编译器优化
和执行模式部分偏向于理论基础,重点掌握知识点.
需了解 内存模型各部分作用,保存哪些数据.
类加载双亲委派加载机制,常用加载器分别加载哪种类型的类.
GC分代回收的思想和依据以及不同垃圾回收算法的回收思路和适合场景.
性能调优常有JVM优化参数作用,参数调优的依据,常用的JVM分析工具能分析哪些问题以及使用方法.
执行模式解释/编译/混合模式的优缺点,Java7提供的分层编译技术,JIT即时编译技术,OSR栈上替
换,C1/C2编译器针对的场景,C2针对的是server模式,优化更激进.新技术方面Java10的graal编译器
编译器优化javac的编译过程,ast抽象语法树,编译器优化和运行器优化.
2、知识点详解:
1、JVM内存模型:
线程独占:栈,本地方法栈,程序计数器 线程共享:堆,方法区
2、栈:
又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方 法出口等信息.调用方法时执行入栈,方法返回式执行出栈.
3、本地方法栈
与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.
4、程序计数器
保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行 Native方法时,程序计数器为空.
5、堆
JVM内存管理最大的一块,对被线程共享, 目的是存放对象的实例,几乎所欲的对象实例都会放在这里, 当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理
6、方法区:
又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7 的永久代和1.8的元空间都是方法区的一种实现
7、JVM 内存可见性
3、说说类加载与卸载
(1)加载 :把字节码通过二进制的方式转化到方法区中的运行数据区
(2)连接:
验证:验证字节码文件的正确性。
准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了
解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)
(3)初始化 :执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。
(4)卸载:卸载类
4、简述一下JVM的内存模型
1.JVM内存模型简介
JVM定义了不同运行时数据区,他们是用来执行应用程序的。某些区域随着JVM启动及销毁,另外一 些区域的数据是线程性独立的,随着线程创建和销毁。jvm内存模型总体架构图如下:
JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。下图是根据自己理解画的一个 JVM内存模型架构图:
5、说说堆和栈的区别
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在 区域不连续,会有碎片。
1、功能不同
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变 量,还是类变量,它们指向的对象都存储在堆内存中。
2、共享性不同
栈内存是线程私有的。 堆内存是所有线程共有的。
3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。 栈空间不足:java.lang.StackOverFlowError。 堆空间 不足:java.lang.OutOfMemoryError。
4、空间大小
栈的空间大小远远小于堆的。
6、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言” ?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字 节码文件 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
7、如何判断对象可以被回收?
判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计 数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引 用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对 象。
8、说一下 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 收集器可以高效地管理内存,通过分区域回收的方式,减少垃圾回收的停顿时间,同时避免内存碎片化问题。
9、什么情况下会内存溢出?
堆内存溢出:(1)当对象一直创建而不被回收时(2)加载的类越来越多时(3)虚拟机栈的线程越来越多时
栈溢出:方法调用次数过多,一般是递归不当造成
10、对象的创建过程
(1)检查类是否已被加载,没有加载就先加载类
(2)为对象在堆中分配内存,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。
(3)初始化,将对象中的属性都分配0值或null
(4)设置对象头
(5)为属性赋值和执行构造方法
11、说说什么是垃圾回收GC?
GC 是 Garbage Collection 的缩写,即垃圾回收。它是 Java 虚拟机(JVM)自动管理内存的一种机制。在 Java 程序运行过程中,会不断创建对象,当这些对象不再被使用时,如果不进行回收,就会占用内存空间,导致内存泄漏等问题。GC 的作用就是自动识别并回收这些不再使用的对象所占用的内存,使程序能够高效地运行,而无需程序员手动管理内存。
12、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 ,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
四、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 个槽),每个节点负责一部分槽,支持自动故障转移。