1、IOC底层实现原理
xml解析、工厂模式、反射。
Spring IOC两种实现方式:BeanFactory和ApplicationContext
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供给开发人员使用。加载配置文件的时候不会创建对象,在获取的时候才会创建
ApplicationContext:BeanFactory的子接口,提供更多更强大的功能,一般由开发人员使用,加载配置文件的时候,就会把xml文件中的对象创建。
ApplicationContext接口有实现类。
2、SpringBoot自动装配原理
@SpringBootApplication -> @EnableAutoConfiguration -> AutoConfigurationImportorSelector(@Import({AutoConfigurationImportSelector.class})),该类中的方法selectImport方法会扫描所有存在META-INF/spring.factories的jar包。
3、Spring容器存放的都是代理对象吗
不是。
- spring的ioc 容器中默认都是原生对象,只有通过aop增强的对象才是代理对象。
- 配置了aop的类或者类中方法上有@Transactional注解的(因为@Transactional注解的原理就是基于aop的)。
4、SpringBoot整合RabbitMQ
1、Hello World方式
//消息队列消费者
@Component
//队列默认持久化 非独占 不是自动删除的
@RabbitListener(queuesToDeclare = @Queue(value = "hello", durable = "true", autoDelete = "true"))
public class HelloCustomer {
//回调函数处理消息
@RabbitHandler
public void receive1(String msg) {
System.out.println("msg" + msg);
}
}
//消息队列生产者
通过RabbitTemplate发送消息到hello队列
rabbitTemplate.convertAndSend(routingKey:"hello",object:"hello world");
2、worker模式
//消息队列消费者
@Component
public class WorkCustomer {
//一个消费者
@RabbitListener(queuesToDeclare = @Queue("work))
public void receive1(String msg) {
System.out.println("msg1" + msg)
}
//一个消费者
@RabbitListener(queuesToDeclare = @Queue("work))
public void receive2(String msg) {
System.out.println("msg2" + msg)
}
}
//消息队列生产者
通过RabbitTemplate发送消息到work队列
rabbitTemplate.convertAndSend(routingKey:"work",object:"work模型");
3、广播模式
//消息队列生产者
通过RabbitTemplate发送消息到logs队列
rabbitTemplate.convertAndSend(exchange:"",routingKey:"",object:"Fanout模型");
@Component
public class FanoutCustomer {
@RabbitListener(bindings = {
@QueueBinging(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "logs", type = "fanout") //绑定的交换机
)
})
public void receive1(String msg) {
System.out.println("msg1" + msg);
}
@RabbitListener(bindings = {
@QueueBinging(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "logs", type = "fanout") //绑定的交换机
)
})
public void receive2(String msg) {
System.out.println("msg2" + msg);
}
}
5、OSI七层架构和TCP/IP
osi:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
tcp:应用层、传输层、网络层、网络接口层
6、sql执行顺序
from > on > join > where > group by > 函数 > having > select > distinct > order by > 分页
7、强引用、软引用、弱引用、虚引用
- 强引用:即使发生OOM内存空间满了也不会去回收,比如new出来的对象。
- 软引用:空间不足了才会去回收
- 弱引用:不管空间够不够用,进行一次gc就会回收
- 虚引用:如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。 所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收了。
8、Java对象创建到毁灭过程
类加载检查 -> 分配内存 -> 初始化零值 -> 设置对象头 -> 执行init方法 -> 对象晋升(年轻代)-> 垃圾回收
9、FullGC
老年代空间不足,新生代设置太小,新生代设置过大,
发生FULL GC的时候,意味着JVM会安全的暂停所有正在执行的线程(Stop The World),来回收内存空间,在这个时间段内,所有除了回收垃圾的线程外,其他有关JAVA的程序,代码都会静止。
10、SQL优化
创建合适索引、避免索引失效、只返回需要的结果、优化多表连接、尽量避免使用子查询、优化集合操作
11、原子性实现
为了实现原子性,需要通过日志:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已经执行成功的操作撤销,从而达到“全部操作失败”的目的。
保持隔离性:乐观锁和悲观锁
12、熔断、限流、降级
- 降级(丢车保帅):在秒杀环境下把修改用户信息、注册等非核心功能关闭。
- 熔断:支付需要依赖第三方服务,设置熔断策略,策略会给出友好提示,如请十分钟后重试。
- 限流:抢购1000件商品,当队列超过2000,就对后续请求直接拒绝。
sentinel三种流控规则:直接拒绝、关联、链路
sentinel三种流控效果:快速失败、Warm up、排队等待
两种统计类型:QPS、线程数
13、fallback和blockHandler区别
- fallback:若本次接口出现未知异常,则调用fallback指定的接口
- blockHandler:若本次访问被限流或降级,则调用blockHandler指定的接口
14、Error和Exception区别
都是继承Throwable,Error是指正常情况下不可能出现的情况,出现了不可恢复,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
15、IO、BIO、NIO、AIO
- IO:input/output,翻译过来就是输入/输出。
- BIO:Blocking I/O 同步阻塞I/O模式,数据读写必须在一个线程内等待其完成。
- NIO:New I/O nio是一种同步非阻塞的IO模型,Non-Blocking
- AIO:Asynchronous I/O AIO也称为NIO2,它是异步非阻塞IO模型。
16、URI和URL区别
- URI:(Uniform Resource Identifier)统一资源标识符
- URL:(Uniform Resource Location)统一资源定位符
URI相当于身份证,可以标识这个人,URL相当于住址,可以通过URL找到这个人。
17、DNS解析过程(TCP/UDP,传输时使用TCP,其他时候使用UDP)
- 浏览器搜索DNS缓存。
- 没有,搜索操作系统中的缓存或者hosts文件。
- 没有,依次本地域名服务器 -> 顶级域名服务器 -> 权限域名服务。
- 返回IP给操作系统,缓存IP。
- 返回给浏览器,缓存IP。
- 浏览器得到域名相应的IP地址。
18、用户态到内核态
- 系统调用
- 外设中断
- 异常
19、死锁
- 互斥
- 请求和保持
- 不可剥夺
- 循环等待
20、maven生命周期
- clean生命周期(清理项目)
- default生命周期(构建项目)
- site生命周期(建立和发布项目)
21、mybatis选择标签
- if判断
<where>
<if test="null != statusCode and 0 != statusCode">
AND b.STATUS_CODE = #{statusCode, jdbcType=VARCHAR}
</if>
</where>
2、choose判断
<choose>
<when test="5 == queryType">
CASE WHEN statusCode IN (1,6) THEN 1 ELSE 2 END,createTime DESC
</when>
<when test="1 != queryType and 9 != queryType">
createTime DESC
</when>
<otherwise>
<choose>
<when test="null == orderType or 1 == orderType">
auditTime
</when>
<when test="2 == orderType">
downloadCount
</when>
<when test="3 == orderType">
browseCount
</when>
<otherwise>
is666Count
</otherwise>
</choose>
<if test="null == orderMode or 1 == orderMode">
DESC
</if>
</otherwise>
</choose>
当when标签都没匹配,则会执行ortherwise
21、ConcurrentHashMap
-
Node+CAS+Synchronized
-
hashtable和Collections.synchronizedMap()都是直接上锁,性能没有最优解。
-
也就是说初始化数组、数组下标为null时候采用的都是CAS操作来保证线程安全的,但是当当前下标不为空的时候,就采用synchronized来保证线程安全了。
-
当数组初始化或者数组下标为null时候,其实CAS要比较的就一个对象,相对比较简单,但是一旦有哈希冲突后,再用CAS来做Compare And Swap的时候,需要比较的对象就太多了
-
concurrentHashMap的key,value不可以为null,hashmap可以
-
尾插法,弱一致性
-
线程安全
22、Java参数传递方式
- 参数传递 (char、byte、short、int、long、boolean、float、double)加上String
- 引用传递(除String外所有的对象)
23、查询
BETWEEN AND 能匹配指定范围内的所有值,包括起始值和终止值,not between and不包含
SELECT
sum(case when snum<60 then 1 else 0 end) AS 不合格,
sum(case when snum>=60 AND snum<70 then 1 else 0 end) AS 中,
sum(case when snum between 80 AND 90 then 1 else 0 end) AS 良,
sum(case when snum>=90 then 1 else 0 end) AS 优
FROM test.Score;
select for update对数据库行上面添加一个行级锁,当一个事务未完成时,其他事务只可以读不可以修改。
间隙锁 例: id 1 2 5 10, update where id <6会锁所有小于6的,如3,4
24、HashMap如何定位桶数组的位置
HashMap在key,get,put键值的时候,会先对key调用hash(key)处理,然后才会是取模运算定位桶数组的位置。最后将键值对添加到链表或者红黑树中。
25、ReentrantLock
- AQS(抽象队列同步器,独占、共享)
- 双端队列
- state
- 支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞
- 公平锁和非公平锁。何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO(公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换)
26、协程
- 为了提高IO并发。
- 无需系统自动切换,用户手动切换,在同一个栈上执行,速度快节省资源。
- 协程只能串行化执行
27、synchronized 特性
- 原子性
- 可见性
- 有序性
- 可重入性
重入锁的核心功能委托给内部类Sync实现,并且根据是否是公平锁有FairSync和NonfairSync两种实现。这是一种典型的策略模式。实现重入锁的方法很简单,就是基于一个状态变量state。这个变量保存在AbstractQueuedSynchronizer对象中。
Condition可以理解为重入锁的伴生对象。它提供了在重入锁的基础上,进行等待和通知的机制。
重入锁的伴生对象Condition提供了await()和singal()的功能,可以用于线程间消息通信
27、synchronized和lock区别
- lock是接口,synchronized是关键字
- synchronized会自动释放锁,lock不会(发生异常,死锁),需要try-catch finally unlock
- 因为synchronized获取不到锁的时候会阻塞,并且阻塞不可被打断的特性会导致可能会产生死锁的问题,为了解决这个问题,Java就提供了Lock锁的实现,从主动放弃获取锁或者被动放弃获取锁的方式,解决一直阻塞可能产生的死锁问题。
ThreadLocal和synchronized区别:时间换空间,空间换时间。ThreadLocal使得每个线程某一时间访问的并不是同一个对象,需要在一定场景下可以使用,即允许备份的场景下。
28、 分段和分页区别
29、查询关键字前后100行
- cat filename | grep 关键字 -C100
30、事务隔离性及实现原理
介绍:
- 原子性:语句要么执行,要么不执行
- 持久性:保证事务提交之后不会因为宕机等原因导致数据丢失
- 隔离性:保证事务执行尽可能不受其他事务的影响
- 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障。
实现原理:
- 原子性:回滚日志(undo log)
- 隔离性:锁机制、数据的隐藏列、undo log和类next-key lock机制(锁和MVCC机制)
- 一致性:是数据库追求的最终目的,通过原子性、隔离性、持久性来保证一致性。
- 持久性:重做日志(redo log)
redo log(物理日志):是innodb特有的,具有崩溃恢复能力。
bin log(归档日志、逻辑日志):是server层的归档日志,不足以实现崩溃恢复(所有引擎均有)。
- binlog是追加写入。追加写是binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。那么就会出现没有标志能让innodb从binlog中判断哪些数据已经刷入磁盘。
- redolog是循环写入,即空间固定会被用完,那么只会记录未刷入磁盘的日志,已经刷入磁盘的数据就会删掉。redo log采用两阶段提交。即redo log的写入拆分为了2个步骤:prepare和commit;
31、回表
回表查询:使用普通索引查询时,当前索引树上的叶子节点存放的数据不是查询所需的全部数据时,此时就需要通过主键key返回主键聚簇索引树上查询所需要的数据,此时称为回表查询;与之对应的,如果查询的数据在普通索引树中能够全部找到,那么称之为覆盖索引;
32、hashCode和equals
hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
33、YoungGC、OldGC、FullGC
(1)YoungGC 就是在新生代的Eden区域满了之后,就会触发,采用复利算法来回收新生代的垃圾
(2)发生YoungGC之前往往会先检查一下老年代的空间,如果说明本次YoungGC后可能升入老年代对象的大小,可能超过了老年代当前可用内存空间,此时必须先触发一次OldGC给老年代腾出更多的空间,然后再执行YoungGC
所以,一般OldGC 很可能就是在 YoungGC 之前触发,所以自然OldGC一般都会跟一次YoungGC连带关联在一起了。 那他触发的实际上就是FullGC,因为我们知道 FullGC会包含YoungGC、OldGC和永久代的GC 也就是说触发FullGC的时候,可能就会去回收年轻代、老年代和永久代三个区域的垃圾对象。
34、垃圾回收器
- Serial
- ParNew
- G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
- CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
35、消息乱序
生产者发送消息的时候,把自己上一条消息ID记录到放到消息体中。在消费者接收到消息之后,首先检查此消息中的上一条消息ID属性。如果不存在上一条消息ID属性,那么就正常消费操作。如果成功消费,则将此次消费的消息ID记录到数据库中。如果存在上一条消息ID属性,那么就去数据库查看上一条消息是否已经成功被消费。如果成功被消费,那么就执行正常的逻辑。消费并记录本次消费ID。如果发现上一条消息没有被成功消费,那么可以稍等一下,在程序中执行睡眠X秒的操作。当休眠时间到了,再去检查一下上一条消息是否已经被成功消费,那么就抛出异常,将当前的消息会推到原队列,并等待下次重新消费。
36、MySQL8和MySQL5的区别
1、8支持隐藏索引,可以通过设置隐藏索引测索引效率,去除无效索引。
2、8编码改成了utf8mb4,支持emoji表情。
3、8修改了持久化设置,可以设置永久持久化。
4、8提升了性能,约是5的两倍。
5、窗口函数,更多函数可以少写代码多实现功能。
37、MVCC
mvcc全程多版本并发控制,这项技术使得innodb事务隔离级别下执行一致性操作有了保证,换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到他们被更新前的值。这是一个可以用来增强并发性的强大技术,因为这样的一来的话查询就不用等待另一个事务释放锁。(实际上根据版本号)
MVCC实现原理:依赖记录的三个隐式字段、undo log、ReadView
对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。
-
READ COMMITTED —— 每次读取数据前都生成一个ReadView
-
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
-
在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;
-
在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View
38、redis和mysql数据一致性
1、延时双删(主从库时会读到从库脏数据)
2、先更新数据库,再删除缓存(读操作快于更新操作,很难出现)
3、删除缓存重试机制(避免延时双删第二次删除缓存失败)
39、upstream配置方式
- 轮询(用于后端性能不均的情况)
- ip_hash(根据ip的hash结果分配,每个访客固定访问一个后端服务器,解决session不能跨域的问题)
- fair(按后端服务器的响应时间来分配,响应时间短的优先分配)
- url_hash(按访问的url结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存服务器时比较有效)
40、JVM堆栈和数据结构堆栈的区别
数据结构中的堆是完全二叉树,堆中的元素是有序的,二叉树中双亲结点和孩子节点存在着大小关系,有大根堆和小根堆。栈一种特殊的线性表,只能先进后出,只允许在一端操作。
jvm中的栈和堆对应内存的不同区域。
41、InnoDB行级锁实现原理
位图。InnoDB引擎支持的行级锁,其开销非常的小,并且不需要锁升级。在InnoDB引擎中,锁的标识不是存储在行记录上的,而是在页上通过位示图的方式存储的。位图的本质就是二进制数组,它的每一位对应一行记录,而每一位的值若为0则代表无锁,为1则代表有锁。
42、GC Roots包含哪些
GCRoot对象
- 虚拟机栈中引用的对象
- 方法区类的静态成员引用的对象
- 方法区常量引用的对象
- 本地方法栈中JNI(一般说的native方法)中引用的对象
引用计数法:每次对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗,较难处理循环引用。
43、事务隔离级别
1、串行化:一个事务在执行过程中完全看不到其他事务对数据库所作的更新。当两个事务同时操作数据库相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化运行的。
2、可重复读:一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
3、读已提交:一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新。
4、读未提交:一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新。
44、阻塞IO和非阻塞IO(两个阶段:数据准备和数据读写)
- 阻塞IO,当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态。
- 非阻塞IO,当用户线程发起一个read操作之后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。非阻塞IO不会交出CPU。
- 多路复用IO:在IO多路复用中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。(select和poll很相近,用法几乎一致。epoll既高效又不用内存拷贝)
阻塞IO
非阻塞IO:用户线程可以发起IO请求后可以立即返回。
45、TCP同时打开、同时关闭
- 同时打开:当同时打开的情况时,状态迁移图就与标准的的迁移图不一样。两端几乎同时发送SYN并进入SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变成了ESTABLISHED。一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。
- 同时关闭:当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。两端收到FIN之后,状态均变为CLOSING(CLOSING 状态是由于同时关闭导致的),同时发送ACK,当两端同时收到ACK之后,状态变为TIME_WAIT。
46、HashMap扩容
1.7扩容后每个元素需要rehash,链表中元素顺序每次迁移后被倒置。1.8中在扩容HashMap的时候,不需要像1.7中去重新计算元素的Hash,只需要看看原来的hash值新增的那个二进制bit是1还是0,如果是0的话表示索引没变,是1的话表示索引变成“OldCap + 原索引(原位置+旧容量)”,这样即省去了重新计算hash值的时间,并且扩容后链表元素位置不会倒置。
正是因为这样巧妙的rehash方式,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,在resize的过程中保证了rehash之后每个桶上的节点数一定小于等于原来桶上的节点数,保证了rehash之后不会出现更严重的hash冲突,均匀的把之前的冲突的节点分散到新的桶中了。