面试准备

2022/01/01 面试 共 8351 字,约 24 分钟

2022年,面试准备个人总结,涵盖热门面试知识点,不涉及详细分析,持续更新中。

整体模块大致分为Java基础、多线程、框架、Redis、MQ、MySQL、架构设计、数据结构和算法、中间件等,会根据面试内容不断完善。

JAVA基础

对象创建的方式

创建方式 
使用new关键字调用了构造函数
使用Class类的newInstance方法,基于反射调用了构造函数
使用Constructor类的newInstance方法,基于反射调用了构造函数
使用clone方法,浅拷贝没有调用构造函数
使用反序列化没有调用构造函数

对象引用的类型

引用类型说明被垃圾回收时间用途生存时间
强引用如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。比如String str = “hello”这时候str就是一个强引用。如果想要中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。不回收一般状态JVM停止
软引用内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap在内存不足时缓存内存不足时
弱引用如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收在GC时,不管内存是否不足缓存一次回收过后
虚引用虚引用是Java中最“弱”的引用,在任何时候都可能被垃圾回收器回收。通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。 如果一个对象具有虚引用,就相当于没有引用,在任何时候都有可能被回收。使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。跟踪回收活动

对象的生命周期

  • 创建阶段(Creation)
  • 应用阶段(In Use)
  • 不可视阶段(Invisible)
  • 不可达阶段(Unreachable)
  • 可收集阶段(Collected)
  • 终结阶段(Finalized)
  • 对象空间重分配阶段(De-allocated)

类加载过程

虚拟机加载类主要有五个过程:加载、验证、准备、解析和初始化。

字节码

JVM

Java内存模型JMM

Java 内存模型是一种规范,定义了很多东西:

  • 所有的变量都存储在主内存(Main Memory)中。
  • 每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝副本。
  • 线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存。
  • 不同的线程之间无法直接访问对方本地内存中的变量。

为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现:

  • lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

注意:工作内存也就是本地内存的意思。

虚拟机内存模型

线程共享:堆、方法区

非线程共享:虚拟机栈、本地方法栈、程序计数器

Java堆 = 老年代 + 新生代 = Eden + S0 + S1

GC

设置堆内存,-Xms最小,-Xmx最大;新生代和老年代比例,合适的GC算法、GC日志、OOM自动dump

java8默认并行垃圾收集器(Parallel GC),9之后是G1

排查

  • 收集不同的指标(CPU,内存,磁盘IO,网络等等)
  • 分析应用日志
  • 分析GC日志
  • 获取线程转储并分析
  • 获取堆转储来进行分析

多线程

线程生命周期:新建(new)-就绪(runable)-运行(running)-blocked(阻塞)-死亡(dead)

线程池

配置参数如下:

(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

锁的类型原理场景/特点
乐观锁CAS写少读多
悲观锁每次获取资源加锁写多读少
公平锁多个线程按照申请锁的顺序来获取锁吞吐率低
非公平锁多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待吞吐率高,线程饿死
重入锁同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁ReentrantLock
非重入锁非可重入可能死锁
独享锁互斥锁,锁一次只能被一个线程所持有大部分锁
共享锁锁可被多个线程所持有;读写锁中的读锁
自旋锁循环等待,然后不断的判断锁是否能够被成功获取,默认10次 
适应性自旋锁自旋次数不固定 

AQS

AQS核心作用
内部类Node有共享或独占模式、waitStatus等属性
FIFO队列多线程争用资源被阻塞时会进入此队列
int state同步状态,可自定义具体实现
isHeldExclusively()是否独占模式
tryAcquire(int)独占方式。尝试获取资源,成功则返回true,失败则返回false
tryRelease(int)独占方式。尝试释放资源,成功则返回true,失败则返回false
tryAcquireShared(int)共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
tryReleaseShared(int)共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
实现AQS

从上面可以看出自定义同步类的实现条件

  • 实现Lock接口,lock()和 unlock()方法,具体实现依赖Sync
  • 自定义Sync(继承AQS)作为内部类,内部类实现tryAcquire()和tryRelease()。

常用类

CountDownLatch、ReentrantLock、ReadWriteLock、Semaphore

ThreadLocal、AtomicInteger

Spring/SpringBoot

对象的生命周期

AOP原理

动态代理

@Autowire和@Resource区别

@Autowire bytype,@Resource byName(推荐使用)

BeanFactory和FactoryBean

对比BeanFactoryFactoryBean
类型接口接口
作用生产bean的工厂,基础型IoC容器能生产或者修饰对象生成的工厂Bean,实现该接口可以定制实例化bean
主要方法getBean(String name)
getBean(Class requiredType)
isSingleton(String name)
getObject()
getObjectType()
isSingleton()

BeanFactory和ApplicationContext区别

ApplicationContext继承了BeanFactory,BeanFactory是Spring中比较原始的Factory,它不支持AOP、Web等Spring插件,而ApplicationContext不仅包含了BeanFactory的所有功能,还有强大的事件机制(Event)、底层资源的访问、国际化。

Springboot自动配置

image-20220321175401454

循环依赖

image-20200706161709829

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

放入三级缓存时,暴露获取bean引用的getEarlyBeanReference方法。这个方法里面内部的实现有兴趣可以研究一下,我就直接说结论了,由@Async注解的类,在这里暴露的是原生的bean,而不是代理bean,所以当你的bean最后初始化生成代理bean时,别的bean用的是你的原生bean,这个时候spring就会给你报错。而@Transaction注解在getEarlyBeanReference方法中暴露的时代理bean的引用。

微服务框架

Springcloud、dubbo比较

功能组件Spring CloudDubbo
底层Http协议Rest接口通信Netty NIO框架,TCP协议,Hession序列化完成RPC通信
通讯协议restdubbo、rest、grpc、Thrift,还有Dubbo3、dubbo2等
性能报文更大占用带宽大,但rest灵活性能较好,但强依赖

服务治理

组件原理

架构设计

架构图

image-20220324174438773

秒杀系统

设计方案实现
独立部署独立web服务器,后台服务、独立域名
页面设计简单简化页面商品信息
多级缓存浏览器或APP缓存、CDN缓存、服务端本地缓存、分布式缓存
下单限流全局下单计数器(redis实现)

数据结构和算法

限流算法

限流算法原理优点缺点场景
固定窗口统计一个周期内的请求量实现简单临界点问题普通限流
滑动窗口一个周期分为多个小窗口限流粒度较细临界点问题,实现稍复杂普通限流
漏桶桶里是请求,总量控制桶大小可控,可应对突发流量无法精确控制流出速度秒杀
令牌桶桶里是令牌,请求从桶里拿令牌,速率控制速率可控突发大流量会丢弃许多请求,实现复杂平稳限流,如访问第三方服务

一致性Hash算法

算法关键点实现
环形hash空间2^32个桶
数据映射把数据(对象)通过一定的hash算法处理后映射到环上
机器映射将机器通过hash算法映射到环上,顺时针方向,把数据存储到最近机器
机器删除该机器的数据会顺时针迁移到下一个机器,其他数据不变动
机器增加部分数据存储到新机器,需要迁移
虚拟节点一个实际节点可以对应多个虚拟节点,数据分布均衡

#####

Redis

常用数据结构包括String、list、set、zset、hash、bitmap、Hyperloglog(基数)、geospatial(地理位置)。

持久化

持久化方式原理优点缺点
RDB定期快照存储体积小、恢复速度快,适合备份丢失部分数据
AOF追加写,记录每次对服务器写的操作数据完整文件体积大,速度慢
混合模式内存快照以⼀定的频率执⾏,在两次快照之间,使⽤ AOF ⽇志记录不会出现⽂件过⼤的情况了,也可以避免重写开销 

zset有序集合

跳表是链表加多级索引的结构,原理是单向链表的二分查找。

img

部署方式

部署方式原理和作用缺点
单机单机部署单机故障,性能差
主从主从读写分离:
主从均可读
写交给主库,然后同步到从库(全量、增量)
作用:数据冗余、负载均衡
主库挂了不能提供写,故障不能自动转移
哨兵哨兵集群:哨兵是⼀个运⾏在特殊模式下的 Redis 进程
监控:周期性地给所有的主从库发送 PING 命令
哨兵选举:主观客观下线、
故障转移:筛选打分三轮选主(优先级、同步最接近、ID最小)
通知: 从库同步新主库,原主库下线
作用:高可用
 
集群⽤哈希槽来处理数据和实例之间的映射关系,key使用crc16算法对16384取模后定位到槽
客户端缓存hash槽信息,重定向机制move
有服务端集群和客户端集群,cluster是服务端集群
 

渐进式Hash

Redis 默认使⽤了两个全局哈希表:哈希表12。默认使⽤ 1, 2 并没有被分配空间。随着数据逐步增多,Redis 开始执⾏ rehash

  • 给哈希表 2 分配更⼤的空间
  • 把哈希表 1 中的数据重新映射并拷⻉到哈希表 2 中(渐进式,只写入新表,先查老表查不到再查新表)
  • 释放哈希表 1 的空间

LRU缓存淘汰算法

自己实现:单向链表,头部放最新数据。判断是否新数据(遍历是否存在),如果已存在,删除所在的节点,然后放在头节点;如果是新数据,判断是否有内存,没有的话就删除尾部节点,新数据都是放在头节点的。

缓存穿透、雪崩

 缓存穿透缓存击穿缓存雪崩
问题来源缓存和数据库中都没有的数据某个key缓存中没有但数据库中有,大量并发缓存key大批量失效,而查询数据量巨大
场景非法请求,id不存在或被删了缓存时间到期大量key失效;redis宕机
解决方法接口层增加校验
缓存空值,过期时间短
布隆过滤器判断key是否存在,不存在就返回
设置热点数据永远不过期
接口限流与熔断,降级
key更新加全局互斥锁
缓存过期时间设置随机
设置热点数据永不过期
热点数据均匀分布在不同的缓存数据库中

Mysql

  • 行锁:MVCC行级锁,对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,使得读取时可以完全不加锁。
  • 表锁:锁粒度大,并发低
  • 死锁:

索引

B+树特点:m 叉树只存储索引(非叶子节点),并不真正存储数据,通过双向链表将叶子节点串联在一起,这样可以方便按区间查找。IO次数取决于b+数的高度h,3层的b+树可以表示上百万的数据。

索引优化:索引命中率、索引长度、组合索引(最左匹配)

事务

隔离级别

  • 读取未提交:最低的隔离级别,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读
  • 读取已提交: 只能读取到已经提交的数据。可以阻止脏读,但是幻读或不可重复读仍有可能发
  • 可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • 可串行化:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行。

MySQL 默认采用的 可重复读 隔离级别。

SQL优化

  • 分析语句,是否加载了不必要的字段/数据。
  • 分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
  • 查看 SQL 涉及的表结构和索引信息。
  • 如果 SQL 很复杂,优化 SQL 结构。
  • 按照可能的优化点执行表结构变更、增加索引、SQL 改写等操作。
  • 查看优化后的执行时间和执行计划。
  • 如果表数据量太大,考虑分表。
  • 利用缓存,减少查询次数。

MQ

常用MQ中间件包括Kafka、RocketMQ、ActiveMQ、RabbitMQ、Pulsar等。

名称优点缺点
RabbitMQ轻量级,支持多语言,路由规则灵活性能差,消息积压不友好
RocketMQ性能高,响应时延低周边生态不足
Kafka吞吐量高,支持大数据和流计算时延高
Pulsar存储和计算分离的设计,性能高,时延低
无缝扩展到超过百万个topic
多种订阅模式:
持久化消息存储机制保证消息的送达
数据老化时,分层冷热存储
流数据处理
未知

pulsar

路由包括:轮询分区,单一分区、定制化分区

订阅模式:独占、共享和灾备

Zookeeper

节点特性

  • 同一级节点 key 名称是唯一的
  • 创建节点时,必须要带上全路径
  • session 关闭,临时节点清除
  • 自动创建顺序节点
  • watch 机制,监听节点变化
  • delete 命令只能一层一层删除

应用场景

  • 数据发布/订阅
  • 负载均衡
  • 分布式协调/通知
  • 集群管理
  • master 管理
  • 分布式锁
  • 分布式队列

分布式锁

排它锁:在lock节点下创建临时子节点 ,只有一个客户端创建成功获取锁,未获取锁的节点同时在lock节点下创建子节点的watch变更事件,可以重新获取锁。

共享锁:创建临时顺序节点,对于写请求,如果自己不是序号最小的子节点,那么就进入等待;对于读请求如果所有比自己小的子节点都是读请求可以获取锁。

系统排查

分布式技术

分布式事务

Seata模式对比AT模式TCC模式
场景支持本地 ACID 事务的关系型数据库
Java 应用,通过 JDBC 访问数据库
不依赖于底层数据资源的事务支持
整体机制一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁
二阶段:提交异步化,回滚通过一阶段的回滚日志进行反向补偿
两阶段提交模型:
一阶段 prepare 行为
二阶段 commit 或 rollback
一阶段事务1:获取本地锁,update语句,获取全局锁,提交本地事务,释放本地锁
事务2:获取本地锁,update语句,重试等待全局锁
调用 自定义 的 prepare 逻辑
二阶段提交事务1:全局提交,释放全局锁
事务2:等到全局锁,提交本地事务,释放本地锁
调用 自定义 的 commit 逻辑。
二阶段回滚事务1:重新获取本地锁(等待2释放),反向补偿更新提交
事务2:等待全局锁超时,回滚本地事务,释放本地锁
调用 自定义 的 rollback 逻辑

分布式锁

排它锁mysqlzookeeperredis
原理表锁(不推荐):写入一条记录,插入成功的获取到锁,删除记录释放锁
乐观锁:行锁,CAS
X节点下创建子节点,只有一个client成功,其他节点创建子节点变更事件,重新获取锁redission实现多种锁
优点简单、容易实现高可用,不可重入高可用、高性能
缺点单机故障、性能差、死锁实现复杂,性能低于redis锁失效时间控制不稳定,稳定性低于ZK
场景适合并发低、性能不高场景适合大部分场景,性能要求不高适合高性能、高并发场景

设计模式

创建型设计模式原理场景
单例模式创建全局唯一的对象 
工厂模式创建不同但是相关类型的对象(继承同一父类或者接口的一组子类) 
建造者模式创建复杂对象,通过设置不同的可选参数定制化创建 
结构型设计模式原理场景
代理模式不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问监控、统计、鉴权、限流、事务、幂等、日志、RPC、缓存
装饰者模式主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能IO
适配器模式提供跟原始类不同的接口,用来做适配的,它将不兼容的接口转换为可兼容的接口补救设计上的缺陷
门面模式通过封装细粒度的接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性,或者解决性能、分布式事务等问题 
行为型设计模式原理场景
观察者模式将观察者和被观察者代码解耦,又叫发布订阅模式邮件订阅、RSS Feeds
模板模式在一个方法中定义一个业务逻辑骨架,并将某些步骤推迟到子类中实现AbstractList addAll()
策略模式定义一个策略接口和一组实现这个接口的策略类,策略的创建由工厂类来完成,客户端运行时根据type动态确定使用哪个策略避免冗长的 if-else
职责链模式多个处理器依次处理同一个请求过滤器、拦截器
迭代器模式也叫游标模式,它用来遍历集合对象,作用是解耦容器代码和遍历代码集合迭代器
状态模式它由 3 个部分组成:状态、事件、动作,事件触发状态的转移及动作的执行工作流引擎

网络协议

TCP三次握手四次挥手

文档信息

搜索

    Table of Contents