Java常见面试题汇总(日常更新)

1.反射:

        1> 反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法,

             这种JVM动态获取信息和调用对象方法的功能称之为反射.

        2>  常用实例:   1.原生JDBC加载数据库驱动是标准的通过反射动态获取信息;     

                                2.Spring框架的IOC原理底层运用工厂模式通过反射生成对象等;   

        3>  优点: 优点显而易见动态执行,动态调用,方便灵活.     

              缺点: 通过反射执行动作需要解析字节码,将内存中的对象解析出来,比直接执行代码创建内存对象慢,一定程度上会影响性能.

        4>  解决方案:   1.关闭JDK安全检查提升反射速度;    2.对常用的实例对象,做缓存处理;

2.集合

集合分为collection单列集合和map双列集合,单列集合又分为list和set接口,list分为arraylist,linkedlist,vector,ArrayList底层基于数组,查找快,增删慢。

LinkedList底层基于链表,增删快,查找慢,Vector是线程安全的,所以效率低下,set接口分为hashset,linkedhashset,hashset底层为哈希表,无序集合,不可重复。

LinkedHashSet底层为哈希表和链表结合,有序集合,不可重复。双列集合分为hashmap,linkedhashmap,hashtable,hashmap线程不安全,效率较高,支持key-value为null值,

linkedhashmap继承HashMap,为hashmap子类,保存插入顺序,hashtable 由数组+链表组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的,为线程安全,效率低,且key-value都不能为null

3.多线程

并发编程三要素:原子性,可见性,有序性

创建多线程的方式:

  1. 继承Thread类 : 重写run()方法执行体,创建Thread线程对象,调用start方法开启线程;
  2. 实现Runable接口 : 重写run()方法执行体,创建Runnable线程对象,通过Runnable对象创建Thread目标对象,调用start方法;
  3. 实现Callable接口 : 实现Callable接口:创建Callable接口实现类,实现call()方法,创建Callable实例,使用FutureTask作为Thread的目标对象创建线程,并调用get()方法获取Callable的Call方法的返回值;
  4. 通过线程池创建;

创建方式优缺点:

使用接口创建多线程的优势 : 使用接口创建可以继承其他类,多个线程可以共享同一个目标对象,所以适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想;

继承Thread类的优势 : 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程

Runnable和Callable的区别:

Runnable接口中run()方法没有返回值,其方法只考虑执行run方法中的代码,Callable的call()方法可以通过FutureTask异步获取执行的结果返回值

创建线程的7个参数:

  1. corePoolSize:线程池的核心线程数量
  2. maximumPoolSize:线程池中允许的最大线程数
  3. keepAliveTime:当池中线程数大于核心线程数时,该时间为余下线程(存活线程总数-核心线程数)的最大空闲存活时间
  4. unit:时间单位
  5. workQueue:工作队列,存放将要执行的任务的地方
  6. threadFactory:创建线程的线程工厂
  7. handler:达到线程上限或者队列容量上限时执行的拒绝策略。

线程池的五种类型:

  1. newFixedThreadPool:固定数量线程池
  2. newWorkStealingPool:并行线程池
  3. newSingleThreadExecutor:只有一个线程的线程池
  4. newCatchedThreadPool:缓存线程池
  5. newScheduledThreadPool:延时线程池

线程的生命周期和状态:

1>新建状态: 创建线程对象, Thread t = new MyThread();

2>就绪状态: 处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。

3>运行状态: 就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。

4>阻塞状态: 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

5>死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

乐观锁(CAS)和悲观锁:

CAS是一种基于锁的操作,属于乐观锁,乐观锁采取宽泛的态度,悲观锁是将资源锁住,等到之前获得锁的线程释放锁之后才允许下一个线程访问,相比较悲观锁来说CAS的性能比较高.

synchronized属于抢占式悲观锁,线程安全的标志字符,会引起线程阻塞。

CAS内部如何运行:操作包含三个操作数:内存位置,原值和新值,如果内存地址里值等于A,则更新B.此操作为无限循环执行,如果当前线程获取的地址里面的值被其他线程修改了,则当前线程要到下次循环才能修改回来.

缺点:

        1>CAS容易造成ABA问题,可滚动版本号解决 ;

        2>不能保证整个代码块的前后一致性 ,通过volatile关键字保证代码原子性;

        3>因为无限循环,所以会一直占用cpu资源;

什么是自旋:

很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略.

volatile的作用:

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

sleep和wait方法的区别:

sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视,wait方法会释放锁需要调用notify方法才能唤醒线程.

单例模式:某个类的实例在多线程环境下只会被创建一次出来,懒汉式(线程不安全),饿汉式以及双检锁单例模式.

死锁:

两个请求线程彼此阻塞,在申请锁的时候发生交叉闭环,造成死锁(A线程持有a锁没有释放去申请b锁,B线程持有b锁没有释放申请a锁);

死锁的四个必要条件:

  1. 互斥条件:在一段时间内某资源仅为一个线程所占有,此时若有其他线程请求该资源,则请求线程只能等待。
  2. 不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己主动释放。
  3. 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
  4. 循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。

如何防止死锁:

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  • 死锁检测
  • 避免在一个同步方法中调用其它对象的延时方法和同步方法

4.IO

java定义了专门负责输入输出的流对象,所有的输入流都是InputStream(字节流)或Reader(字符流)的子类,所有的输出流都是OutputStream或Writer的子类;因为java的字符是Unicode双字节编码,InputStream字节流处理字符文本不方便,所以引入一套字符流.

IO和NIO的区别:

  1. 两者最大的区别是IO面向流的,NIO面向缓冲区.IO每次从流中读取一个或多个字节,直至读取完所有字节,他们不会被缓存在任何地方,也不能前后移动流中的数据;NIO会将数据读取到缓冲区,需要时可以在缓冲区前后移动,增加数据处理过程的灵活性.
  2. IO中各种流是阻塞的,也就是说当一个线程调用read或write时,线程会被阻塞,直到数据读写完成后线程才会被释放,NIO是非阻塞的,当一个线程从从通道读取数据时,如果没有数据可用,该线程可以做其他事情,直至数据可以读取时,才进行读写操作.所以一个线程可以管理多个输入输出通道

字节流和字符流的区别:

字符流处理的单元为2个字节的Unicode字符,操作字符,字符数组或字符串,字节流处理单元为1个字节,操作字节和字节数组.所以字符流是由java虚拟机将字节转化为2个字节的unicode字符为单位的字符而成,如果是音频文件,图片歌曲等用字节流避免数据丢失,如果是中文文本则用字符流好些;字符流以字符或字符数组的形式读写数据,只能读写二进制文件,字节流可以读写各种类型数据.

IO和NIO的使用场景:

因为NIO是面向缓冲区操作,所以每次处理数据前都要判断缓冲区的数据是否完整或者是否读取完毕,因此结合NIO单独线程可以管理多个输入输出通道的特性可以在需要打开千万数量级的链接,且发送数据量少的场景下使用,比如通讯服务器,聊天;如果只有少量链接,且每次发送的数据量比较大,使用传统IO有更好的效果,可以结合数据读取响应时间和缓冲区检查数据时间来权衡选择.

什么叫对象序列化,什么是反序列化,如何实现对象序列化:

对象序列化:将对象以二进制的形式保存到硬盘上;

反序列化:将二进制文件转化为对象读取.

将需要序化的类实现Serializable接口

5.事务

 事务的四大特性:(ACID)

        1>原子性(Atomicity):指一个事务所有操作要么做完,要么不做,不存在中间环节;

        2>一致性Consistency):事务在执行前后,数据保持一致;

        3>隔离性(Isolation):隔离多个事务并发交叉执行防止数据不一致;

        4>持久性(Durability):事务一旦结束,数据永久性修改;

事务的隔离性:

        1>脏读:a修改age为21,b查询到21后,a回滚事务;

        2>幻读:a查询age>20的员工,b插入一条age=30的数据,a发现数据查询多了一条;

        3>不可重复读:a查询age为21,b修改为22后,a二次查询age变为22;

        4>丢失更新:a修改age为22,b修改age为23,a二次查询发现age变为23;

事务的隔离级别

        1>读未提交:最低隔离级别,指不同事务之间可以读取到未提交的数据;

        2>读已提交:高于前者,指事务之间只能读取已提交的数据,可以避免脏读;

        3>可重复读:指各事务可对同一字段多次,读取的结果一致,可以避免脏读,不可重复读;

        4>可串行化:最高事务隔离级别,完全串行化执行事务,可以避免脏读,不可重复读,幻读;

6.JVM

JVM分为两个子系统类加载器,执行引擎和两个组件运行时数据区,本地方法库本地接口;

运行时数据区 : 由虚拟机栈,本地方法栈,程序计数器,以及java堆和方法区五部分组成,其中java堆和方法区是线程共享区,其他三个区域为线程隔离区

运行流程:首先编译器将代码转换为字节码class文件,之后通过类加载器将字节码加载到内存中的方法区内,但字节码文件只是JVM的一套指令规范,操作系统并不能直接识别,需要通过命令解析器执行引擎来解析后再交给CPU执行,解析过程中需要调用其他语言的本地库接口来实现.

java堆:是虚拟机内存最大的一块区域,被所有线程共享,所有的对象实例,都在堆内分配内存;

虚拟机栈:存储局部变量表,操作数栈,动态链接,方法出口等数据;

本地方法栈:同虚拟机栈,但本地方法栈为虚拟机调用Native方法服务;

方法区:存储已经被加载过得类信息,常量,静态变量和编译后的代码;

程序计数器:字节码的行号指示器,字节码解析器就是通过改变计数器的值来选取下一条需要执行的字节码指令;

堆和栈的区别:

        1> 堆的物理地址分配对对象是不连续的,性能要慢,栈遵循后进先出原则,物理地址分配是连续的,性能快;

        2> 堆内存分配是在运行期确认的,比栈大且不固定,栈内存在编译期就确认,固定大小;

        3> 堆存放对象实例,更关注数据的存储,栈存放局部变量,操作数栈,返回信息,更关注程序执行;

        4> 堆内对象全线程共享可见,栈线程私有;

队列和栈的区别:  队列是后面进前面出,栈进出都是在栈顶,队列采取先进先出原则,栈是后进先出方式.

创建对象的几种方式:

  1.  使用new关键字
  2. 使用Class的newInstance方法
  3. 使用Constructor的newInstance方法
  4. 使用clone方法
  5. 使用反序列化

创建对象的过程:(类加载)

当在main方法中创建对象时,JVM会先去方法区下找有没有所创建对象的类存在,有就创建,没有就把该类加载到方法区;在创建类的对象时,首先去堆内存中开辟一块空间,开辟完空间后分配该空间,当空间分配完成后,加载对象中所有的非静态成员变量到该空间下,所有的非静态变量加载完成后,对所有的非静态变量进行初始化,初始化完成后,调用相应的构造方法到栈中,在栈中执行构造函数时,先执行隐式,再执行构造方法中的代码。

内存泄漏:

堆内存溢出:

        设置的jvm内存太小,对象所需内存太大,创建对象时分配空间,就会抛出堆内存泄漏异常;

        应用程序自身处理存在一定限额,当数据激增或者超过预期阈值时,会触发堆空间异常;

        解决方案:  调整jvm堆内存大小,参数Xmx,Xms;避免较大对象内存申请,比如文件上传,大批量数据数据库获取等;提高请求速度,优化gc效率;        

堆内存泄漏:

        Java中的内存泄漏是一些对象不再被应用程序使用但垃圾收集无法识别的情况。因此,这些未使用的对象仍然在Java堆空间中无限期地存在。不停的堆积最终会触发内存泄漏异常

        常见的造成泄漏的原因: 单例对象持有一个不再被使用对象的引用;非静态内部类里创建一个静态实例;线程的匿名内部类持有Activity隐式引用;资源未关闭;静态集合容器不清空;

        解决方案:

                        对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量;

                        保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

                        对于不再需要使用的对象,显示的将其赋值为null

栈内存溢出:

        当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据,当方法递归调用自身时,每次递归便会创建一个新的栈帧,随着无数次调用,栈中的内存越来越少,就会造成栈内存溢出.

        解决方案:    如果程序中确实有递归调用,出现栈溢出时,可以调高-Xss大小,就可以解决栈内存溢出的问题了。递归调用防止形成死循环,否则就会出现栈内存溢出。

GC机制:

       在java中,程序员不需要刻意释放一个对象的内存,由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。  

如何判断对象是否可以回收:

        引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
        可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

GC机制把堆内存分成三部分,分别为:

新生代(Eden区(对象出生区),Survivor from区(一次GC幸存者),Survivor to区(二次GC幸存者): 

新生代通常用来存放新创建的对象,一般占堆内存的1/3,因为频繁创建对象,所以会频繁触发GC进行回收;

老年代:生命周期较长的对象,老年代比较稳定,不会经常进行GC操作;

永久代:永久保存区域,一般存放Class和元数据,不会进行GC操作;

为什么要设置两个Survivor区:

        因为如果没有,Eden对区每进行一次GC,就会把幸存者送到老年代里,老年代被填满后就会触发Full GC,消耗的时间要比Eden的GC时间长很多,Survivor存在的意义就是减少幸存者送到老年代的对象数量,从而减少Full GC的发生。两个Survivor最大的好处是解决碎片化,Eden区经历一次GC后就可以把存活的对象全部移动到第一块Survivor中,然后可以清空Eden区。当Eden再次满载后,再次触发GC,Eden区存活的对象和S0中的存活对象被复制送到第二块Survivor1中,这样保证了S1中来自S0和Eden两部分存活对象占用连续的内存空间,从而避免了碎片化。

GC垃圾收集算法有哪些:

  1. 标记清除算法:先标记出所有需要回收的对象,之后统一回收
  2. 分代收集算法:把堆内存分为新生代和老年代,根据各个年代特点进行回收
  3. 复制算法:将内存划分成相等的两块,每次只使用一块,用完后,将存活的对象复制到另外一块后将在用的一块清空
  4. 标记压缩算法:标记过程与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

JVM垃圾回收器:

CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

CMS收集器和G1收集器的区别:

  1. CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
  2. G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
  3. CMS收集器以最小的停顿时间为目标的收集器;
  4. G1收集器可预测垃圾回收的停顿时间
  5. CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
  6. G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

JVM调优

常用工具:

  1. jconsole:用于对 JVM 中的内存、线程和类等进行监控
  2. jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等

 常用参数:(设置内存大小,设置堆gc比例,指定垃圾回收器)

  1. -Xms2g:初始化推大小为 2g;
  2. -Xmx2g:堆最大内存为 2g;
  3. -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
  4. -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
  5. –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;

7.Spring框架

 Spring是一个轻量级的企业应用框架,它的核心思想为IOC和AOP.

IOC为控制反转,意为通过Spring框架来创建对象,底层通过工厂模式使用反射的机制来创建对象,且支持加载服务时的饿汉式初始化和懒加载,通过注解注入把代码量降到最低,可      以松散耦合度; 

AOP为切面类编程,在框架内执行某些操作时,可以将要做的一些交叉逻辑或校验规则封装成一个切面在操作触发之前或之后先执行,这种操作节点事件的特性称之为切面类编程。

AOP代理模式有两种:JDK动态代理和CJLIB代理;

触发增强有五种通知:前置,后置,异常,返回,环绕通知,包含整条业务线所有需要aop的事件间断;

织入: 把切面应用到目标对象并创建新的代理对象的过程,AspectJ在编译期内织入切面的,SpringAOP在运行期内织入;

Spring AOP和AspectJ AOP的区别:

  1. AOP实现的关键在于代理模式,AOP代理主要分为静态和动态代理
  2. AspectJ是静态代理的增强,静态代理是AOP框架在编译阶段生成AOP代理类,也称为编译时增强,它会在编译阶段将AspectJ织入到Java字节码中,运行的时候就是增强后的AOP对象
  3. Spring AOP使用动态代理,动态代理是AOP框架无需自己修改字节码,它会在每次运行时在内存中临时为方法生成一个代理对象,这个代理对象包含了目标对象的全部方法,并且在特定的切点做了增强,并回调原对象的方法.

JDK动态代理和CGLIB动态代理的区别:

  •  JDK动态代理只提供接口代理,不支持类的代理,核心InvocationHandler接口和Proxy类,InvocationHandler通过invoke方法反射调用目标类代码,动态将逻辑和业务编织在一起,Proxy利用InvocationHandler动态创建符合接口的实例,生成目标类代理对象
  • 如果代理类没有实现InvocationHandler接口,则AOP会使用CGLIB来代理目标类.CGLIB是一个代码生成的类库,可以在运行时动态生成指定类的子类对象,并覆盖其中特定方法并添加增强代码,实现AOP,CGLIB通过继承的方式做动态代理,所以如果一个类被final修饰了,则无法通过CGLIB方式做动态代理

使用@autowired自动装配的过程:

使用注解前需要先在Spring配置里注册需要注入的类,在启动spring ioc时,容器自动装载一个AutowiredAnnotationBeanPostProcesser后置处理器,当容器扫描到@Autowired,@Resource,@Inject时,会在IOC容器自动查找需要的bean,如果查不到,则会抛异常,如果查到两个,则按照名称二次查找,如果只查到一个则指定给@Autowored指定数据并装配该对象属性

Spring Beans

Spring Beans是形成spring应用主干的对象,被IOC容器初始化,装配和管理,这些beans通过容器中配置的元数据创建。

Spring bean的五种作用域:

1>singleton:bean在每个IOC容器中只有一个实例;

2>prototype:一个bean可以定义多个实例;

3>request:每次http请求都会创建一个bean对象,该作用域只在web的SpringApplicationContext下有效;

4>session:在一个http session中,一个bean对象只对应一个实例对象,作用域也只在web的SpringApplicationContext下有效

5>global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

此处应用可以通过注解方式,修改作用域为prototype将Controller从单例转为多例(@Scope(“prototype”))。

Spring bean的生命周期: (实例化,填充属性,初始化,销毁)

  1. 实例化: Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化 

  2. 填充属性: Bean实例化后对将Bean的引入和值注入到Bean的属性中

  3. 初始化: 如果实现了Aware接口,则通过不同类型的Aware接口取到Spring容器的资源;

                      如果实现了BeanPostProcessor接口,则回调该接口的postProcessBeforeInitialization和postPrrcessAfterInitialization()方法;

                      如果配置了init-method方法,则会执行该方法;

    4. 销毁:如果Bean实现了DisposableBean接口,则调用destory接口方法;

                如果配置了destory-method声明销毁方法,则会执行该方法;        

BeanFactory 和 ApplicationContext的区别

 BeanFactory和ApplicationContext是Spring的两大核心接口,都可以做Spring容器,ApplicationContext是BeanFactory的子接口

 依赖关系:BeanFactory作为spring最底层的接口,负责定义各种bean对象,bean配置读取,bean属性加载,实例化,控制生命周期,维护各bean对象依赖关系等;

                   ApplicationContext作为BeanFactory的子接口,拥有BeanFactory所有功能外,还提供了更完整的框架功能:

                        1>继承了MessageSource,支持国际化;  

                        2>统一的资源访问方式;

                        3>提供监听器中注册bean的事件;

                        4>可以同时加载多个配置文件;

                        5>载入多个上下文;

加载方式:BeanFactory采用延迟加载的形式来注入bean,只有在使用到某个bean的时候,才对该Bean进行实例化,ApplicationContext则是在容器启动时,一次性创建所有Bean,这样在容器启动阶段就可以发现有哪些bean创建失败.当程序对象比较多时,ApplicationContext启动就比较慢

创建方式 : BeanFactory通过编程方式创建,ApplicationContext还可以以声明方式创建,比如ContextLoader

注册方式 : BeanFactory需要手动注册,ApplicationContext是自动注册

Spring事务:

  • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  • 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务

SpringMVC:

SpringMVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型视图控制器分离,将web层解耦,简化开发,方便使用.

  • 模型(Model )封装了应用程序的数据和一般他们会组成的POJO。

  • 视图(View)是负责呈现模型数据和一般它生成的HTML输出,客户端的浏览器能够解释。

  • 控制器(Controller )负责处理用户的请求,并建立适当的模型,并把它传递给视图渲染。

MVC的运行流程:

Java常见面试题汇总(日常更新)_第1张图片

  1. 用户请求发送到前端控制器(Servlet)
  2. 前端控制器带着请求调用处理器映射器
  3. 处理器映射器会去找对应的处理器,生成处理器对象并将对象返回给前端控制器
  4. 前端控制器带着请求和处理器对象调用处理器适配器,处理器适配器带着请求找对应的处理器(Controller)
  5. 在Controller中调用业务层Service进行业务处理,调用持久层访问数据库处理数据后返回处理对象(ModelAndView)
  6. 后端控制器带着处理对象返回给前端控制器
  7. 前端控制器会带着对象给到视图解析器
  8. 视图解析器对返回的数据进行解析和渲染后,响应用户

SpringMVC和Struts2的区别:

  1. SpringMVC的前端控制器是servlet,struts2是filter
  2. SpringMVC使用方法的形参接收请求参数,基于方法开发,线程安全,单例模式开发,struts2通过类的成员变量接收参数,基于类开发,线程不安全,多例模式开发
  3. SpringMVC通过参数解析器解析request的内容,并封装数据为ModelAndView对象,Struts2采用值栈存储请求和响应数据,通过OGNL存取数据
  4. SpringMVC是Spring生态的一部分,不需要整合可以直接使用,Struts2则需要整合Spring后方能使用

SpringBoot微服务

SpringBoot是Spring开源组织的子项目,是Spring一站式解决方案,他简化了Spring的配置,提供了各种启动器

SpringBoot的优点:

  1. SpringBoot使用约定大于配置理念,让项目得以快速成型,且能独立运行在服务器;
  2. SpringBoot内嵌了servlet容器,这样我们就不用以war的形式部署项目;
  3. SpringBoot提供starter简化maven配置,会自动加载所需要的依赖包
  4. SpringBoot会根据类路径的jar,为其中的类自动装配bean对象,可以减少大部分配置信息

SpringBoot自动装配的原理:

在SpringBoot的启动类有一个注解@EnableAutoConfigration里有一个AutoCOnfigurationImportSelector.class实现了ImportSelector接口,其中接口里给容器导入了META-INF下的spring.factories定义的自动配置类,通过这个配置类结合对应的properties配置文件进行对象自动装配。

开启SpringBoot特性的两种方式:

  1. 继承spring-boot-starter-parent项目
  2. 导入spring-boot-dependencies项目依赖

8.MQ消息队列

        消息队列相比于传统的串行,并行方式,其异步处理的方式提高了系统吞吐量,系统间通过队列进行消息通信,不用关心系统的其他处理,起到松散耦合的效果.还可以通过消息队列长度来控制请求量,可以缓解短时间内高并发问题,达到流量削峰的效果,可以解决大批量日志传输问题,一般消息队列都内置了高效的通信机制,所以可以应用于单纯点对点消息通讯.

RabbitMQ的工作模式:

  1. simple模式:消息生产者产生消息,将消息放入队列,消息消费者监听消息队列,如果有消息,则消费掉,并删除队列中的消息
  2. work工作模式:消息生产者将消息防毒队列,多个消费者同时监听同一个队列,谁先拿到谁消费消息
  3. 发布订阅模式:生产者将消息发送给交换机,由交换机将消息转发到每个队列中,每个消费者监听自己的队列进行消费
  4. routing路由模式:消息生产者将消息发送给交换机按照路由字符判断,交换机根据路由的key,匹配路由key对应的消息队列,由对应消费者消费.

如何保证RabbitMQ的顺序性: 拆分成多个队列,每个队列一个消费者

消息如何分发:若队列中至少有一个消费者订阅,消息将以循环的方式发送,通过路由可以实现多消费的功能

消息基于什么传输:RabbitMQ使用信道方式传输数据,信道是建立在真实TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制

如何保证消息消费时的幂等性:

        正常情况下,消费者在消费完毕消费后,会发送一个确认消息给队列,队列知道被消费后,就会将消息从队列中删除

因为网络传输等原因,确认消息没有传给队列,导致队列没有删除消息,将消息再次发送给了消费者

解决方案:在写入消息队列是,对数据做唯一标识,消费者消费消息时,根据唯一标识和消费方的日志记录判断是否消费过

如何保证消息正确发送到RabbitMQ,如何确保接收方消费了消费:

发送方确认机制:信道设置为发送方确认模式(confirm),所有在信道发布的消息都会被指派一个唯一ID,消息被消费者接到或者被写入磁盘后,信道会发送一个确认信息给生产者.如果发生错误导致消息丢失,也会发送一条未确认消息

接收方确认机制:消费者接受每条消息都必须进行确认,只有确认了,MQ才会把消息从队列中删除.RabbitMQ只通过和消费者的连接中断确认是否需要重新发送消息,只要不中断就不会再次发送

如何保证RabbitMQ消息的可靠性:

  1. 生产者丢失消息:RabbitMQ提供transaction和confirm模式确保消息不会丢失,使用事务的方式数据吞吐量会下降
  2. 消息队列丢失消息:消息持久化 ,将queue持久化表示durable设为true,发送消息时将deliveryMode设置为2
  3. 消费者丢失消息:由自动确认改为手动确认消息

如何保证高可用性:集群

普通集群模式:在多台机器上启动多个MQ实例,每台机器启动一个,你所创建的队列,只会放在一个MQ实例上,但每个实例都同步队列的元数据,所有的队列里消息是互通的,你消费的时候,实际如果连接了另外一个实例,那么那个实例会从队列所在实例上拉取数据过来,以此来提高吞吐量,让集群中多个节点来服务队列的读写操作

镜像集群模式:和普通集群模式不一样,镜像模式下,你创建的队列,无论元数据还是队列里的消息都会存放在多个实例上,也就是每个MQ节点都会有一个完整的队列镜像,每次写消息到队列的时候,都会自动把消息同步到多个实例的队列上,在MQ后台可以增加镜像集群模式的策略,可以配置数据同步到所有节点还是指定节点,在某一台机器宕机时,消费者可以去其他节点消费数据,但这样配置,消息需要同步到所有机器,对性能会大大打折,加大了网络带宽压力和消耗

9.LINUX常用命令

  1. 查看所有java进程: ps | ef grep java
  2. 杀进程: kill -9 12345
  3. 查看日志 tail -f catalina.out
  4. 查看端口占用: netstat -anp | grep 8080
  5. 查看端口使用情况: netstat -nultp
  6. 查文件位置: find / -name xxxx.txt   //  locate xxxx.txt
  7. 修改文件权限: chmod 777 /home/ekp/xxxx.txt
  8. 创建文件夹: mkdir abcd
  9. 备份文件: cp -r ekp ekp2021823
  10. 删除文件: rm -f ekp  递归级联删除: rm -rf ekp
  11. 查看服务器内存占用: free -m
  12. 修改配置文件:vim xxx.txt

10.MYSQL

mysql的锁: 表级锁(加锁快,不会死锁,锁冲突概率高,并发低),行级锁(加锁慢,会死锁,锁冲突概率低,并发度高)

数据库三大范式:

  1. 第一范式就是属性不可分割
  2. 第二范式就是要有主键,要求其他字段都依赖于主键
  3. 第三范式就是要消除传递依赖,方便理解,可以看做是消除冗余

ACID靠什么保证:

  1. A 原子性 由undo log日志保证,它记录了需要回滚的日志信息,事务回滚是撤销已经成功执行的sql
  2. C 一致性 由其他三大特性保证、程序代码要保证业务上的一致性
  3. I 隔离性 由MVCC来保证
  4. D 持久性 由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log 恢复

mysql数据库中MyISAM和InnoDB的区别: 

  • MyISAM:读取快,增删慢,不支持事务,支持表级锁,存储表的总行数,执行count时不需要全表扫描,采用非聚集索引,索引的数据域存储数据文件的指针
  • INNODB:支持事务和事务特性,支持行级锁和外键约束,不存储表总行数,支持自增列属性auto_increment,其为处理大量数据最高性能设计,比其他基于磁盘的关系型数据库不能匹敌,采用聚集索引,索引数据域存储数据文件本身

为什么InnoDB必须要有主键,且推荐使用整型自增主键

  • 为了满足Mysql索引的B+tree结构特性,需要有索引作为主键,能有效提高查询效率.如果不指定主键,InnoDB会从数据列中找一列不重复的作为主键索引,如果没有,则会在后台增加一列rowid作为主键索引,所以不如自己设置主键,便于使用
  • 在设置时,推荐使用整型,占用磁盘空间更少,查询起来也更容易,使用字符串需要转换ASCII后再比较,无疑会降低性能
  • 因为B+tree是多路多叉树,如果主键索引不是自增的,则平衡索引的工作就落到B+tree的身上了,会影响数据插入效率

数据库优化:

  1. 选择适应的字段属性,减少字段宽度,尽量避免字段属性为null
  2. 使用join连接代替子查询,用union代替手动创建临时表
  3. 建立索引,事务优化,使用外键关联,优化查询语句

SQL优化:

  1. 查询时,避免全表扫描,使用具体字段查询,在where或者order by涉及的列建立索引
  2. 用exists替代in,用not exists 代替not in
  3. 避免在索引列上使用计算,使用is null,is not null
  4. 对于枚举类型字段,使用enum代替varchar
  5. 使用模糊查询避免使用双%,双%会导致索引失效
  6. 避免在where条件里判断null或使用表达式操作,避免索引失效进行全表扫描

描述索引,主键,唯一索引,联合索引的区别和对性能的影响:

  • 索引是数据表中所有记录的引用指针
  • 普通索引用于加快对数据库的访问速度
  • 普通索引允许被索引的数据列有重复数据,想要设置为唯一索引,需要使用关键字UNIQUE
  • 主键是一种特殊的唯一索引,一张表只能定义一个主键索引,使用关键字PRIMARY KEY
  • 联合索引是结合两个数据列字段组成的索引
  • 索引可以提高数据检索速度,唯一性索引能保证数据库表数据唯一性,加速表与表之间连接

SQL注入漏洞和预防:

程序开发时,由于不注意规范sql和特殊字符过滤,造成请求方通过全局变量POST和GET提交一些sql执行操作

预防: 

  1. 开启配置文件中的magic_quotes_gpc和magic_quotes_runtime设置
  2. 执行sql语句时使用addslashes进行sql语句转换
  3. sql编写时不要省略单双引号
  4. 过滤sql语句中的关键词:update,insert,delete,select,* 

Mysql的执行流程:

  1. 客户端发送一个执行sql,首先在连接器检查该语句有没有权限,没有返回错误信息
  2. 权限无误后,查询缓存中是否有历史查询,如果有,返回缓存中的结果,没有进行下一步
  3. 缓存中无数据则进行sql解析,语法检测和预处理,再由优化器生成对应的执行计划
  4. 执行器根据优化器生成的计划执行sql,调用存储引擎接口进行查询
  5. 查询到数据后返回给客户端并保存到缓存中

11.设计模式

设计模式是一套被反复使用,多数人知晓,经过分类编目的,代码设计经验的总结,使用设计模式为了可重用代码,让代码更简洁,可靠.

六大原则: 开放封闭原则(扩展实体实现需求),里氏代换原则(子类实现),依赖倒转原则(接口编程)

                接口隔离原则(多隔离接口),迪米特法则(减少依赖解耦),单一职责原则(独立职能)

简单介绍几种设计模式:

1.单例模式: 保证一个类只有一个实例,提供一个访问全局的访问点,主要使用懒汉和饿汉式

                    饿汉式: 类初始化时会立即加载对象,线程安全,调用效率高

                    懒汉式: 类初始化时,不会初始化对象,只有需要使用时才会创建对象,具备懒加载功能

//饿汉式
public class Demo1 {

    //创建静态私有对象
    private static Demo1 demo1 = new Demo1();
    //创建私有构造方法
    private Demo1() {
        System.out.println("私有Demo1构造参数初始化");
    }
    //创建可调用的创建实例方法
    public static Demo1 getInstance() {
        return demo1;
    }
}

        

//懒汉式
public class Demo2 {

    //创建静态私有不初始化对象
    private static Demo2 demo2;
    //创建私有构造方法
    private Demo2() {
        System.out.println("私有Demo2构造参数初始化");
    }
    //创建静态线程安全的创建实例方法调用
    public synchronized static Demo2 getInstance() {
        if (demo2 == null) {
            demo2 = new Demo2();
        }
        return demo2;
    }
}

/**
 * 双检锁
 * 懒汉式升级版
 */
public class Demo3 {

    private static volatile Demo3 instance;
    
    private Demo3() {}
    
    public static Demo3getInstance() {
        
        //首先判断是否为空
        if(instance==null) {
            //可能多个线程同时进入到这一步进行阻塞等待
            synchronized(Demo3.class) {
                //第一个线程拿到锁,判断不为空进入下一步
                if(instance==null) {
                    /**
                     * 由于编译器的优化、JVM的优化、操作系统处理器的优化,可能会导致指令重排        
                      (happen-before规则下的指令重排,执行结果不变,指令顺序优化排列)
                     * new Demo3()这条语句大致会有这三个步骤:
                     * 1.在堆中开辟对象所需空间,分配内存地址
                     * 2.根据类加载的初始化顺序进行初始化
                     * 3.将内存地址返回给栈中的引用变量
                     * 但是由于指令重排的出现,这三条指令执行顺序会被打乱,
                       可能导致3的顺序和2调换,在instance变量分配了地址,但对象未初始化,则if判        
                       断中结果为true,会造成多次初始化对象问题
                     * 解决方法在instance变量上加volatile,可以禁止变量指令重排,达到线程安全
                     */
                    instance = new Demo3();
                }
            }
        }
        return instance;
    }
}

 单例模式懒汉式和饿汉式的区别:

  1. 饿汉式一旦加载就会初始化对象,保证getInstance时对象已经存在,懒汉式只有调用get时才会初始化对象
  2. 饿汉式是线程安全的,可以直接用于多线程,懒汉式不是线程安全,要用于多线程需要加锁
  3. 饿汉式在创建时就初始化对象,不管用不用,都会占用一定内存,第一次调用速度也比较快,懒汉式反之

2.工厂模式: 工厂模式提供了一种创建对象的方式,在工厂中创建对象不会暴露创建逻辑,使用统一接口创建对象,实现创建者和调用者分离.工厂模式是常用的实例化对象模式,通过工厂方法代替new的操作,有利于降低耦合度

3.代理模式: 通过代理控制对象的访问,可以在调用方法之前,之后添加新的功能,和spring的切面编程相似

  • 静态代理:简单代理模式,是动态代理的理论基础
  • jdk动态代理: 使用反射完成代理,需要有顶层接口才能使用,比如mybatis的mapper文件是代理
  • cglib动态代理:使用反射完成代理,可以直接代理类,使用字节码技术,不能对final类进行继承

4.策略模式:定义了一系列算法或逻辑,将每个算法逻辑操作封装起来,使他们可以相互替换,可以简化if/else应用

使用策略模式,其中的主算法逻辑可以自由切换,避免了大量的重复条件判断,扩展性良好

5.观察者模式:一种行为性模型,又叫发布订阅模式,定义了对象一种一对多的依赖关系,当一个对象改变状态,所有依赖于他的对象都会得到通知并自动更新

12.网络协议 

打开一个网页,计算机做了哪些工作:

  1. 解析域名
  2. 发起TCP3次握手
  3. 建立TCP请求发起http请求
  4. 服务器响应http请求
  5. 浏览器获取到html代码,并解析处理json数据
  6. 浏览器进行渲染

http请求方式: get post head put patch delete options

http和https的区别:

  1.  http是不安全的,https是安全的
  2. http标准端口80,https标准端口443
  3. http无需证书,https需要CA机构的SSL证书
  4. 在OSI网络模型中,http工作于应用层,https安全传输机制工作再传输层

get和post的区别:

  1. get从服务器上获取资源,post向服务器发送数据
  2. get传输数据参数都放在url后面,暴露在外部不安全,post是存放于请求头里,较为安全
  3. get传输的数据量小,虽然url长度受限,但效率更高,post可以传输大量数据
  4. get只支持ASCII字符,传送中文字符需要考虑乱码问题,post支持标准字符集

Session和Cookie的区别:

  1. Cookie保存在客户端,其生命周期随浏览器关闭而删除,且Cookie存放于客户端可以伪造,不安全
  2. Session保存在服务器端,用来跟踪用户的一种手段,每个session都有一个唯一标识,当服务端生成一个session时会像客户端发送一个cookie保存到客户端,并绑定sessionid,这样,当客户端发送请求时,用户能够与服务器端成千上万的session匹配到,Session可以设置过期时间,存储过多会过度消耗服务器资源
  3. Session能够存储任意java对象,Cookie只能存储String类型对象

Http的keep-alive有什么用:   早期的每次HTTP请求都要创建一个连接,频繁的创建会消耗资源和时间,keep-alive机制就是重用连接机制,告诉对方这个响应完成后不要关闭,下次还用这个请求交流,也叫长连接 (Connection: keep-alive)

为什么要三次握手和四次挥手:

三次握手是一个通信连接的过程:请求-->响应-->响应的响应,为什么一定要是三次握手,不是两次或者四次,实际服务端和客户端多次握手为了保证连接的可靠性,客户端请求,服务端同意了,则两次握手完成可以进行通信了.但不排除客户端第一次请求会发生丢包或者网络中断的现象,导致客户端多次发送请求,这样如果仅两次握手服务端会建立多个连接,为了避免这种问题发生,客户端要对服务端的响应再做一次响应,告诉服务端,我收到响应了,别的请求不建立连接了,就是三次握手.

四次挥手是通信关闭的过程:因为TCP是全双工通信的,双方发送和接收都是交叉的过程,所以关闭连接需要关闭两个发送通道和两个接收通道

第一次挥手: 客户端发送断开连接请求后进入FIN_WAIT_1状态,表示客户端不再发送数据,但可以接收数据

第二次挥手: 服务端收到请求后,回复确认进入CLOST_WAIT状态,当客户端收到回复后,进入FIN_WAIT_2状态

第三次挥手: 服务端发送完数据后,告诉客户端我发完了,关闭连接并进入LAST_ACK状态

第四次挥手: 客户端收到响应后,进入TIME_WAIT时间等待状态并回复服务端收到了,服务端就会关闭连接进入CLOSED状态.此时整个TCP连接在客户端还没有完全释放,要经过一个2MSL叫做最长报文段寿命的时间,才会进入CLOSED状态

原因:为了防止网络问题导致服务器没有收到客户端确认关闭的消息,服务端就会一直发送断开连接的请求,客户端会在发送回复后等待2MSL时间,如果在该时间内再次收到服务端断开连接的请求,则会重发确认关闭的请求并再次等待2MSL

TCP和UDP的特点和区别:

  1. TCP是面向连接的,UDP是无连接的
  2. TCP是可靠的,UDP是不可靠的
  3. TCP只支持点对点通信,UDP支持一对一,一对多等通信模式
  4. TCP面向字节流,UDP面向报文
  5. TCP有拥塞控制机制,UDP没有

13.关键字

final: 用于修饰类,属性和方法,呗修饰的类不能被继承,被修饰的方法不能被重写,被修饰的变量不能改变,如果修饰引用,则引用不可变,引用指向的内容可变

finally:一般用在try-catch代码块中,表示不管是否出现异常,都会执行代码,常用以关闭资源

finalize:属于Object类的方法,一般由垃圾回收器调用,当我们手动调用System.gc()时,由gc调用finalize()回收垃圾

this:指向对象本身的一个指针,可以用来指向当前对象本身,区分形参和成员变量重名,引用本类的构造函数

super:指向父类对象的一个指针,用法和this相同,但本质上this是指向本对象的指针,super是java关键字

static:static主要用来创建独立于对象实例的域变量或方法,这样无需创建对象也能调用其方法或使用属性值,它还可以用来生成静态代码块,将只需要初始化一次的操作在类加载初期加载,可以优化程序性能,因为static是类的所有实例对象共享,所以如果一个类里有很多实例都使用了一个成员变量,则可以定义为静态变量.在使用时要注意的是,静态只能访问静态,但非静态即可以访问静态也可以访问非静态

14.变量和方法以及内部类

成员变量:方法外部,类内部定义的变量,存储在对内存中,对象消失后销毁

局部变量:类中方法里的变量,存储在栈内存中,方法调用完后会销毁

实例变量:每次创建对象,都会为对象实例分会内存,实例变量属于实例对象的

静态变量:static变量,被类内所有对象共享,只会在类初次加载时被初始化

静态内部类:静态内部类可以访问外部类所有的静态变量,不可访问非静态变量,通过new外部类.静态内部类创建

成员内部类:可以访问所有静态和非静态的变量和方法,通过new外部类.new内部类创建

局部内部类:定义在非静态方法内的局部类,可以访问所有外部类的变量和方法,直接new

匿名内部类:没有名字的内部类,必须继承一个类或实现一个接口,内部类里不能定义静态成员变量和静态方法,当内部类需要访问局部变量时,变量需要声明为final,因为局部变量时存储在栈中,方法执行结束后,非final的变量会被销毁,那么内部类中变量的引用还会存在,就会出错

15.RPC框架

Dubbo:

  1. dubbo是一个基于java的高性能RPC远程服务调用的分布式服务框架
  2. dubbo支持dubbo,hessian,http,webservice,redis等协议,推荐使用dubbo协议
  3. dubbo内置了Spring,Jetty,Log4j三种服务容器
  4. dubbo架构图 :
  5. Provider:服务提供方 Consumer:服务消费方  Registry: 注册中心  Monitor:监控中心  Container:服务运行容器

Dubbo调用过程:

        服务容器启动,加载运行服务提供者; 服务提供者启动后向注册中心注册服务;  服务消费者启动时向注册中心订阅需要的服务; 注册中心将提供者的ip和端口地址返回给消费者,如果有更新,会基于长连接推送给消费者; 消费者从提供的地址里,基于负责均衡,选一台提供者服务调用; 消费方和服务提供方调用次数和时间,定时发送给数据监控中心

dubbo负载均衡策略:

  • 随机策略: 按权重设置随机概率
  • 轮询策略: 按公约后的权重设置轮询比率
  • 最少活跃调用数: 响应快的提供者接受越多请求,响应慢的接受越少请求
  • 一致hash: 根据服务提供者ip设置hash环,携带相同的参数总是发送的同一个服务提供者,若服务挂了,则会基于虚拟节点平摊到其他提供者上

Zookepper:

  • Zookepper是一个开源的分布式协调服务,其目的就是封装好复杂易出错的关键服务,对外暴露出服务的接口供服务提供方注册和消费服务方调用
  • Zookepper本身维护了一套数据结构,由多个节点组成一个树形结构,节点叫做Znode,可以通过路径唯一标识
  • Zookepper节点分为临时节点,临时顺序节点,持久节点,持久顺序节点
  • Zookepper有多个角色,分别为领导者,跟随者和观察者,也就是四种工作状态:寻找Leader状态,跟随者状态,领导者状态和观察者状态

16.Nosql

 Redis的缓存过期和淘汰策略:

  缓存过期: 

    Redis的数据删除有定时删除、惰性删除和主动删除三种方式, Redis目前采用惰性删除+主动删除的方式。

  1. 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。需要创建定时器,而且消耗CPU,一般不推荐使用。
  2. 惰性删除:在key被访问时如果发现它已经失效,那么就删除它。调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。

  淘汰策略:

     在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来的。
另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。可以想象的是,每一次访问数据的时候,会更新 redisObject.lru。
LRU 数据淘汰机制:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。不可能遍历key 用当前时间-最近访问 越大,说明访问间隔时间越长
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

此处感谢作者,整理内容丰富有深度 : 设计模式面试题 (史上最全、持续更新、吐血推荐) - 疯狂创客圈 - 博客园

你可能感兴趣的:(面试,java,面试,设计模式,反射)