一、Java基础

1.1 线程安全的类有哪些,用来解决什么问题?

  1. 并发包提供的线程安全类:这些类提供了比synchronized更高级的各种同步结构,如ReentrantLockSemaphoreCountDownLatchCyclicBarrier等。这些类通过使用锁或其他同步机制来确保线程安全。
  2. 线程安全的容器:Java提供了多种线程安全的容器,如ConcurrentHashMap、有序的ConcurrentSkipListMap,以及利用快照机制实现线程安全的动态数组CopyOnWriteArrayList。这些容器类通过内部使用锁或其他同步机制来确保在多线程环境下的数据一致性。
  3. 传统的线程安全集合类:包括VectorHashtable。这些类通过内部使用同步代码块或同步方法来确保线程安全,但可能在多线程高并发的情况下性能受到影响。
  4. 新的线程安全集合类:Java5引入了新的线程安全的集合类,如ConcurrentHashMap,它可以在多线程环境下并发的访问和修改数据,且效率比传统的线程安全集合类更高。这些集合类通常使用了读写分离技术来保证线程安全。
  5. 原子类:JDK中提供了如AtomicBooleanAtomicInteger等原子类,它们通过CAS(Compare-and-Swap)操作来保证原子性,从而确保线程安全。
  6. 锁机制:Java还提供了各种锁机制,如synchronized关键字和volatile关键字,以及通过Lock接口实现的锁,这些机制用于保证代码块在同一时刻只能被一个线程执行,从而确保线程安全。
  7. ThreadLocal:虽然不是直接用于确保类的线程安全,但ThreadLocal可以用于确保变量在每个线程中的独立性,从而避免多线程间的数据共享问题,间接地实现了一种形式的线程安全。


1.2 cms 收集器过程,G1 收集器原理,怎么实现可预测停顿的,region 的大小,结构


1.3 内存溢出,内存泄漏遇到过么,什么场景产生的,怎么解决的

内存泄漏:网络请求流没有close;

内存溢出:一次性查询大量数据库数据到内存中,比如100万,改成分批查询

1.4 锁升级过程

无锁->偏向锁->轻量级锁->重量级锁。不会降级

无锁:没有任何加锁的语句

偏向锁:也叫意向锁,比如第一个线程访问synchronized,这个时候加偏向锁,就是标记下

轻量锁:多个线程竞争,通过cas自旋的方式获取锁

重量级锁:如果轻量锁通过自旋的方式无法获得锁,那么升级为重量级锁,除了当前线程外,其他线程都会被阻塞,直到锁释放。重量级锁用来减少对cpu的竞争。

1.5 锁对象头结构变化

01->00->10->11

1.6 synchronized 原理

四大因素

原子性:synchronized范围内的操作要么不执行,要么一次全部执行,不会被打断。

可见性:与volatile类似,一个线程更新的资源对其他线程是可见的。

有序性:JVM会进行优化重排,但是对单线程而言这种优化重排是不会影响程序结果的。synchronized能保证只有单线程访问,所以具有有序性。

可重入性:支持拿到锁的线程,再次重入拿同一个锁。

对象头

无锁    0 01

偏向锁 1 01

轻量锁   00

重量锁   10

GC标记  11

synchronized是用monitorenter和monitorexit指令前后包围(类似pv操作),进入monitorenter的时候申请锁,如果是当前线程持有那么可重入。如果不是当前线程持有,那么当前线程将等待,直到monitorexit释放。


1.7 怎么保证可重入性

https://www.cnblogs.com/-wyl/p/8763120.html



1.8 怎么保证可⻅性,

1.9 抛异常怎么办

1.10 读为什么需要加锁

https://blog.csdn.net/zhouwenjun0820/article/details/107392043

https://www.cnblogs.com/dolphin0520/p/3920373.html

任何加锁的目的都是为了保证原子性,如果不是为了原子性,那么就只要使用volatile来保证可见性和有序性就可以了。

读锁的目的是为了防止读到写的中间值,比如long++

1.11 volatile原理

  • volatile 保证了可见性 - 每次写入同时更新主存,每次读取先同步主存
  • volatile 禁止指令重排,保证了有序性

volatile的使用场景就是double check的单例模式

1.12 CAS的ABA问题是什么

CAS是比较然后交互的意思,一种乐观锁的思想,如果比较失败那么不更新。那如果初始值a=1,线程1想改成a=2,结果线程2做了a=1更新。这个时候线程1来做CAS操作发现a=1,其实它是不能判断线程2有没有更新过的。

1.13 CAS的自旋问题

如果cas操作失败,那么自旋到队列尾部等待下一次调度操作,但是如果多次失败,那么会给cpu带来非常大的开销。

1.14 年轻代和老年代

年轻代再分为3个区域(伊甸园+survivor0+survivor1)(8:1:1),对象首先向伊甸园申请内存,如果伊甸园满了就进行young gc。进行young gc的时候首先将伊甸园的对象拷贝到survivor0,然后清空伊甸园;当survivor0也要满的时候,将伊甸园和survivor0的数据转移到survivor1,然后清空伊甸园和survivor0,此时survivor0是空的,如此反复。

老年代:在年轻代中进过N次回收后依然存在的对象会被放到老年代,因此,老年代中都是一些生命比较长的对象。

老年代一般比较大,一般是年轻代的两倍,当老年代的内存满后会触发Full GC。

1.15 CMS和G1回收算法

先问自己几个问题:

  1. 为什么没有一种牛逼的收集器适配于所有场景?
  2. CMS的优缺点?
  3. 为什么CMS只适合于老年代回收?
  4. G1的优缺点?
  • CMS

CMS是一种获取最短回收停顿时间为目标的收集器(所以适合于老年代),CMS在工作时,GC线程可以和用户线程并发执行,以此来降低收集停顿时间的目的。

CMS仅用于老年代的收集,是基于 标记-清除算法的,它的运作分为4个步骤:

  1. 初始标记(CMS initial mark) - STW(暂停用户线程)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark) - STW
  4. 并发清除(CMS concurrent sweep)

初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快

重新标记是为了修正并发标记期间用户程序正常运行导致标记产生变动的那部分对象的标记记录。

CMS在整个过程中,只有初始标记和重新标记阶段会STW,只有两次短暂的暂停,仅仅需要短暂的时间就可以完成,达到了近似并发的目的。

CMS优点:并发收集、低停顿

CMS缺点:

  • CMS对CPU资源非常敏感,因为gc线程和用户线程是一起运行的,所以对低核cpu机器影响比较大,比如2核的机器,gc线程直接占50%的性能。
  • CMS无法处理浮动垃圾,gc过程中,用户线程还在运行,会产生新的垃圾。
  • CMS是基于标记-清除算法,该算法的缺点都有


  • G1 - Garbage First,首次提出了局部收集的设计思路和基于Region的内存布局

4个步骤:

  1. 初始标记 - STW
  2. 并发标记
  3. 重新标记 - STW
  4. 筛选回收 - STW

总体来说,G1代替CMS是大势所趋

  1. 局部回收代替全堆进行垃圾回收,提高了回收效率
  2. G1采用标记-整理算法,比CMS的标记-清除算法,不会产生内存浮动垃圾

G1缺点:

G1卡表所占空间比较大,由于每个Region都要拥有一张卡表,如此导致记忆集要占20%左右的堆空间,而CMS只需要维护老年代指向新生代的一张卡表即可。

1.16 G1可预测的停顿时间模型

G1把整个JVM内存分为2048个区域,比如堆大小是4096M,那么一个Region是2M

1个Region可能是年轻态,经过回收后可能会变成老年态,这个可以参考CMS模型。

唯一不同的是对大对象的处理,有个专门的地方Humongous存储大对象,所谓的大对象就是超过Region一半大小的对象,比如Region是2M,那么超过1M的就是大对象。如果对象太大,可能会横跨多个Region来存储。

Humongous专门用来存储大对象,不用把大对象放到老年代,避免老年代空间不足导致Full GC

Full GC的时候,会把Humongous,年轻代,老年代一起回收。

比如老年代有1000个Region满了,本次垃圾回收的时间需要控制在200ms内,那么本次就只能回收800个Region,剩下的200个在下次回收。


可预测的回收时间设置的太短行不行?

不行,因为这样每次回收的对象不够,会导致程序运行的时间长了后会触发Full GC,反而会卡顿。


1.17 HashMap为什么使用红黑树而不使用B/B+树?

B/B+树多用于外存上,是一种磁盘友好的数据结构。但是如果数据比较少的情况下,数据可能会挤压到一个节点上,这个时候的查询效率就退化成了链表。


二、MySQL

2.1 mysql 日志文件有哪些,分别介绍下作用

  1. redolog:主要目的是保证数据不丢失,当mysql发生宕机时可以恢复数据。

     为什么要写入redolog,不直接写入磁盘?

     因为顺序io的性能远胜于随机io

     先写入redolog buffer,然后刷盘写入redolog.file和磁盘

     那什么时候写入redolog.file和磁盘呢?

     innodb_flush_log_at_trx_commit=1,默认刷盘策略:每次提交事物成功主动刷盘,写入磁盘和redolog.file。如果事物提交过程中mysql挂了,那么也没事。因为事物还没提交成功,没有写入redolog.file也无所谓。保证ACID的D(持久性),性能最差。

     innodb_flush_log_at_trx_commit=2,只要事物提交成功,那么写入pagecache,不同步写入磁盘和redolog.file,如果这个时候操作系统挂了,会导致pagecache丢失1秒的数据。

    innodb_flush_log_at_trx_commit=0,后台线程每秒提交一次,如果mysql挂了,有一秒的数据丢失风险。效率是最高的。        

  1. bin log: 执行数据库更新操作时记录的日志,比如insert,update,delete,一般用于主从同步
  2. undo log:用于事物回滚
  3. 慢查询日志

2.2 Innodb 磁盘⻚和缓存区是怎么配合


2.3 一致性hash算法

为什么要引入一致性hash算法?对于分表的程序来说,我们一般用这个式子来得到分表

index = hash(code)%n

一般情况下,这个式子运行的不错,但是如果新增节点或者减少节点的时候,也就是n增加或者减少的时候就会出现问题。比如hash(code) = 8,code=5的时候,得到3,如果code=6,那么就会得到2了。

那这个时候怎么解决?就需要引入一致性hash算法。

https://zhuanlan.zhihu.com/p/672309677

分布式环上存储2^32个点,然后顺时间选择自己的点,那么加入或者减少节点后,只会影响一个节点。

Java面试问题分析和答案_java

2.4 一致性hash算法的数据倾斜问题

部分节点宕机后导致大部分请求落在一个节点上,而其他节点分担了很少的请求,如下图:

Java面试问题分析和答案_java_02

解决方法:虚拟节点:上面情况是因为节点不够,但是加节点是要成本的,所以加虚拟节点。ip3-1是ip3的虚拟节点,ip2-1是ip2的虚拟节点。

Java面试问题分析和答案_java_03


2.5 一致性hash算法为什么是2的32次方?

因为IP地址2的32次方


三、Redis

3.1 你们项目为什么用 redis

1.缓存,减少数据库的压力,2. redission做分布式锁

3.2 Redis快在哪?

1.内存操作,2. nio+单线程模式,3.高效的数据结构

3.3 怎么保证高性能,高并发的

  1. 主从模式:

   优点:读写分离,提高效率;数据热备份,提供多个副本;

   缺点:1.主节点故障,导致集群无法正常工作

           2.无法自动调整,需要人工干预

           3.master节点写压力比较大,因为只有一个写节点,从节点都是读节点。

           4.主从同步可能会有性能压力,产生同步风暴      


  1. 哨兵模式

   哨兵模式基于主从模式

   优点:1.可以自动实现故障转移和自动重新配置slave,可以在一定成都实现负载均衡     缺点:1.但是它的性能并没有比主从模式提高多少,主从转化过程中可能有一定的业务故障。


  1. 集群模式

redis集群至少配置3主3从,这样是6个节点。主节点提供读写操作(主从或者哨兵模式下从节点提供读操作),从节点不提供读写操作,只做故障转移使用。

优点:

  • 无中心架构,每个节点负责一个范围的槽位存储,根据hash值计算出槽位(类似hashmap计算规则)。每个master节点负责一块区域槽位的读写,从而横向扩展了读写能力,总量远大于主从模式。
  • 可扩展性,最大可以扩展到1000个节点,节点可以动态添加或者删除
  • 高可用性,部分节点不可达时,可以通过半数节点选举机制完成slave到master的提升
  • 节点间通过gossip协议沟通

缺点:

如果A节点以及它相关的A1从节点都宕机了,那么该集群也无法提供服务了。


3.4 集群模式为什么至少要3个master节点?

因为集群模式某个master节点挂了,需要半数以上的master节点重新选举出新的master节点。如果只有2个master节点,那么1个挂了后,剩余的1个master节点没有达到半数以上的条件(刚好是半数),只有3个master节点,1个挂了后,剩余2个节点刚好是半数以上(>1.5)


3.5 为什么集群模式推荐奇数个节点而不是偶数个节点?

为了考虑节省资源,比如3个或者4个节点,如果2个节点挂了都不能重新选举。所以3个够了,没必要4个。

3.6 Redis集群的插槽为什么设计成16384?

16384/8=2048,刚好是2KB的空间

3.7 Redis集群添加和删除节点后会发生什么

整个集群的数据会重新分配,重新分配槽位;类似与hashmap扩容。所以这种操作最好先备份数据,并充分验证数据。

3.8 Redis八种数据结构

5种基本数据结构

String, List, Set, ZSet(排序的Set), Map

3种特殊类型

HyperLoglog(基数统计-数据集中不重复的元素个数,统计UV,PV),

BitMap(位图,大量的0101,比如用于签到统计,365天有几天签到), 

Geospatial(地理位置)

3.9 Redis hash冲突和rehash

hash冲突:使用拉链法来解决,可能导致链表过长(参考1.7的HashMap解决Hash冲突)

集中式hash - 当hash冲突越来越多的时候,开始rehash,在创建一个2倍的hashmap2,然后把hashmap1的数据拷贝到hashmap2,再把hashmap1的数据清空(类似hashmap的扩容),此时Redis主线程阻塞不响应用户请求。此时hashmap2变成主表,hashmap1待定。

渐进式rehash - 可以一边响应请求一边hash

hash因子 - 5(hash表承载的元素个数是其大小的5倍时扩容)

3.10 Redis集群gossip协议

有趣的协议理论:在有届的网络范围内,如果每个节点随机的和其他节点交换特定信息,经过足够长的时间后集群各个节点对该信息的认知终将达到一致。

优点:

1.理解简单

2.能够容忍集群内的节点随意的增加和减少,因为去中心化,最终会达到一致性

3.扩散速度快,因为是流言蜚语传播,传播速度比只有一个主节点传播快。

缺点:

1.最终一致性的时间难以保证,因为是流言蜚语的传播方式

2.因为是随机传播,所以消息可能会重复

3.拜占庭将军问题

3.11 Redis集群数据有丢失的可能吗

有!比如

  1. 主从节点同时掉线宕机,这个数据就丢失,因为每个节点只保存自己的数据,那么如果它的主从都掉了就,数据就没了。
  2. 主节点掉线后,从节点还没来的及同步主节点的数据。

3.12 Redis哨兵模式选举流程和Raft算法

哨兵模式分为

  • 主节点:数据写入
  • 从节点:数据读取(读写分离)
  • 哨兵节点:负责监控主从节点状态,负责故障检测和通知

选举流程Raft算法:先到先得算法

比如3个哨兵节点进行选举,S1, S2, S3

  • S1向S2/S3发出自己成为新的Leader的申请,S2和S3由于之前没有同意过别的节点,所以直接同意,故S1得2票。
  • S2向S1/S3发出申请,S1同意,而S3之前同意过S1,所以这次S3不能同意,故S2的1票。
  • S3向S1/S2发出申请,S1和S2都已经同意过,故都不能同意,所以S3得0票。

避免脑裂问题,最小选举数量:设置最小投票数,只有在超过一定数量的哨兵节点同意的情况下,才会进行主节点选举和故障转移。

3.13 分布式一致性算法

Paxos算法


Raft算法

  • 领导者会定时向追随者发送心跳,如果一定时间没有收到心跳通知会开始选举新的领导者。
  • 当一个追随者一段时间没有收到领导的通知,就会变成候选者,候选者会增加自己的任期,然后向其他节点发出选票请求,每个节点在一个任期内只能投一票,一般是投给第一个请求投票的的候选者。如果一个候选者收到足够多的选票(一般是过半)就会成为新的领导者。
  • 如果候选者选举失败,那么会重新变成追随者。

3.14 redis 字符串实现原理,SDS和c的区别

https://www.jb51.net/article/225977.htm

Redis字符串为什么不用传统的c语言char*?

因为c语言的char*不能保存所有字符串,它是0结尾,比如"abc\0d",这个字符串长度是3.

而且操作时间复杂度也大,比如就算字符串长度需要o(n)

SDS设计

sds本质上还是字符串数组,只不过在数组的基础上增加额外的元数据,比如查询字符串的长度,因为记录了len长度,所以查询速度可以达到o(1)


3.15 Redis有序集合怎么实现的

Redis用跳表来实现zset(有序的列表),那这种有序的结构为啥不用红黑树或者二叉平衡树呢?

  • 实现方面:跳表更加简单,看起来更加直观,比如平衡树插入或者删除节点可能引起子树的调整,逻辑复杂。而跳表只要修改相邻节点的指针,相对来说操作简单且快速。
  • 性能方面:树需要Rebalance操作,可能涉及整棵树的操作,相对来说跳表只是设计到局部。

Java面试问题分析和答案_java_04

但是这样插入的时候会比较复杂,新插入节点后面所有的节点需要重新调整,这种情况下怎么处理呢?

跳表为了解决插入和删除节点时造成的后续节点重新调整的问题,引入了随机层数(为每次插入的节点随机一个层数)的做法。

Java面试问题分析和答案_java_05

这样每次插入的时候只要更改插入点的前面节点的指针,其他都不要动,也就是说随机层数解决了跳表插入/删除复杂的问题,虽然查询效率会下降。


四、Zookeeper

Zookeeper的灵魂就是Paxos协议,Zookeeper的前提是不存在拜占庭将军问题,也就是整个环境没有叛徒存在。

4.1 ZooKeeper的角色和节点类型

  • Leader:集群中唯一的写请求处理者,只有一个Leader
  • Follower:接收客户端请求,如果是读请求则自己可以处理,如果是写请求则需要转发给Leader来处理,在选举过程中有选举权和被选举权。
  • Observer:没有选举权和被选举权的Follower

4.2 脑裂问题

一个集群内出现多个大脑,也就是多个Leader的情况,比如集群内有两个节点,然后两个节点网络不通,都想把自己拥立为Leader,出现两个Leader,称为脑裂。

Zookeeper为了解决脑裂问题,特意规定节点数为奇数,也就是2N+1,也就是如果出现网络故障,一方的节点数会大于半数,一方的节点数会少于半数。然后只有大于半数投票才可以选举通过,所以ZK里面的Leader要么是1个,要么是0个。

五、Tomcat

5.1 热部署

5.2 热加载


六、JVM

七、kafka

7.1 kafka 重平衡,重启服务怎么保证 kafka 不发生重平衡,有什么方案


八、RocketMQ

九、Nocas

十、Netty

十一、分布式

11.1 分布式和微服务有什么区别?

分布式系统:在分布式系统中,通常使用RPC(远程过程调用)、消息队列等技术来实现子系统之间的通信和协作,可以采用集中式部署或分布式部署方式。

微服务架构:在微服务架构中,通常使用RESTful API、消息总线等技术来实现服务之间的通信和协作,每个服务可以独立部署在不同的容器或虚拟机中,可以采用容器化部署或云原生部署方式。

11.2 秒杀系统 - 令牌桶

令牌桶算法通过动态调整令牌的数量来控制数据的发送速率,是一种实现网络流量控制和速率限制的有效方法。 

前端请求获取令牌,获取到令牌后进入秒杀系统也没,否则显示正在处理中。

11.3 布隆过滤器

https://blog.csdn.net/wuhuayangs/article/details/121830094

布隆过滤器能阻止什么,原理是什么?

布隆过滤器中一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在

Java面试问题分析和答案_java_06

Java面试问题分析和答案_java_07