常见面经整理

文章目录

  • 一、Redis
    • 1、Redis数据类型
    • 2、什么情况下使用redis
    • 3、简述缓存穿透
    • 4、简述缓存穿透的解决方法
    • 5、简述缓存击穿
    • 6、简述缓存穿透的解决办法
    • 7、简述缓存雪崩
    • 8、简述缓存雪崩的解决方法
    • 9、MySQL与Redis区别
    • 10、Redis基本数据类型实现原理
    • 11、Redis快的原因
    • 12、Redis有哪些集群部署方式
    • 13、简述主从复制模式
    • 14、简述哨兵模式
    • 15、cluster集群
    • 16、Redis是如何判断数据是否过期的呢?
    • 17、过期的数据的删除策略了解么?
    • 18、简述Redis淘汰机制
    • 19、快照(snapshotting)持久化(RDB)
    • 20、事务具有四⼤特性: ``(Redis只支持一致性和隔离性)``
    • 21、Redis 事务
    • 22、你知道有哪些Redis分区实现方案?
    • 23、Redis如何做内存优化
    • 24、什么是 RedLock
    • 25、如何保证缓存与数据库双写时的数据一致性
    • 26、Redis常见性能问题和解决方案?
    • 27、Redis数据类型及其使用场景(==面试问题==)
      • (1)String--字符串
      • (2)Hash类型
      • (3)list类型
      • (4)set集合
      • (5)SortSet
    • 28、如何确保redis缓存命中(==面试题==)
      • (1)缓存命中率的影响因素
        • 1.1 业务场景
        • 1.2 时效性
        • 1.3 缓存粒度
        • 1.4 过期时间和更新策略
        • 1.5 缓存预加载-预热
        • 1.6 缓存击穿和缓存穿透
        • 1.7 缓存容量
      • (2)提高缓存命中率的方法
    • 29、redis数据预热
  • 二、Mysql及其优化
    • 1、引擎
    • 2、MyISAM和InnoDB区别
    • 3、索引
    • 4、并发事务带来哪些问题?
    • 5、事务隔离级别有哪些?MySQL的默认隔离级别是?
    • 6、⼤表优化
    • 7、解释⼀下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?
    • 8、分库分表之后,id 主键如何处理?
    • 9、⼀条SQL语句执⾏得很慢的原因有哪些?
    • 10、Mysql小表驱动大表
    • 11、sql 优化分析过程
    • 12、Mysql为什么要用B+树作为存储结构
    • 13、Mysql怎么确保原子性?
  • 三、JVM
    • 1、简述JVM内存模型
    • 2、简述虚拟机栈 ``(线程私有的)``
    • 3、简述本地方法栈
    • 4、简述JVM中的堆 ``(共享区域)``
    • 5、简述方法区 ``(共享区域)``
    • 6、简述运行时常量池
    • 7、简述直接内存
    • 8、简述java创建对象的过程 ``(!!!重点)``
      • Step1:类加载检查
      • Step2:分配内存
      • Step3:初始化零值
      • Step4:设置对象头
      • Step5:执⾏ init ⽅法
    • 9、简述JVM给对象分配内存的策略
    • 10、简述java的引用类型
    • 11、简述标记清除算法、标记整理算法和标记复制算法
    • 12、简述JVM类加载过程
    • 13、简述双亲委派机制
    • 14、双亲委派机制的优点
    • 15、程序计数器
    • 16、如何判断对象是否死亡?(两种⽅法)
  • 四、设计模式
    • 1、简述设计模式七大原则
    • 2、简述设计模式的分类
    • 3、简述简单工厂模式
    • 4、简述工厂模式
    • 5、简述抽象工厂模式
    • 6、简述代理模式
    • 7、简述适配器模式 ``(IO流用的装饰模式、适配器模式)``
    • 8、简述模板模式
    • 9、简述装饰器模式
    • 10、简述观察者模式
    • 11、简述单例模式
    • 12、简述一下你了解的Java设计模式?
    • 13、j2ee常用的设计模式?说明工厂模式。
  • 五、多线程
    • 1、简述线程的可见性
    • 2、简述java中volatile关键字作用
    • 3、java线程的实现方式
    • 4、简述java线程的状态
    • 5、简述线程池
    • 6、简述线程池的状态
    • 7、简述阻塞队列
    • 8、谈一谈ThreadLocal
    • 9、JAVA中的乐观锁与CAS算法
    • 10、Synchronized底层实现原理
    • 11、Synchronized关键词使用方法
    • 12、简述java偏向锁
    • 13、简述轻量级锁
    • 14、线程池类型
    • 15、多线程中的i++线程安全吗?
    • 16、介绍一下生产者消费者模式?
    • 17、简述AQS
    • 18、线程池有什么好处?![在这里插入图片描述](https://img-blog.csdnimg.cn/559bb27c00324d82969fe4e6d91280b1.png)
    • 19、sleep()和wait()有什么区别![在这里插入图片描述](https://img-blog.csdnimg.cn/a1a90b16325c4bfc96b8b516f84d9e8d.png)
    • 20、举例说明同步和异步?![在这里插入图片描述](https://img-blog.csdnimg.cn/780297e7bea64f8dac9b90f24d7b765b.png)
    • 21、如何保证线程安全?![在这里插入图片描述](https://img-blog.csdnimg.cn/e0c8daf1b7744b02a5ed7379dadf921d.png)
    • 22、讲一下非公平锁和公平锁在reetrantlock里的实现?![在这里插入图片描述](https://img-blog.csdnimg.cn/db2ff67848e04590b6ff1f25bc887f2b.png)
    • 23、什么是死锁?![在这里插入图片描述](https://img-blog.csdnimg.cn/8439a45a53054592b2a8284e65c5516c.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/d8b21b777c9442c4adf192f6004cd74a.png)
    • 24、如何保证不会出现死锁?![在这里插入图片描述](https://img-blog.csdnimg.cn/fe6c4cc513b740dc8b7ea37b2756e302.png)
    • 25、为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤ run() ⽅法?
    • 26、说说 synchronized 关键字和 volatile 关键字的区别
    • 27、ThreadLocal 了解么?
    • 28、说说怎么创建一个线程池?(面试题)
    • 29、线程池的饱和策略
    • 30、线程池调优
    • 31、说一下Synchronized修饰静态方法是怎么样?
    • 32、Synchronized和Lock的区别?
    • 33、锁的分类
  • 六、SSM
    • 1、什么是 Spring 框架?
    • 2、列举一些重要的Spring模块
    • 3、谈谈⾃⼰对于 Spring IoC 和 AOP 的理解
    • 4、Spring AOP 和 AspectJ AOP 有什么区别?
    • 5、Spring 中的 bean 的作⽤域有哪些?
    • 6、@Component 和 @Bean 的区别是什么? (面试题)
    • 7、将⼀个类声明为Spring的 bean 的注解有哪些?
    • 8、Spring中的Bean的生命周期
    • 9、说说对SpringMVC的理解
    • 10、SpringMVC工作原理
    • 11、Spring 框架中⽤到了哪些设计模式?
    • 12、Spring 事务中的隔离级别有哪⼏种?
    • 13、Mybatis 是如何进⾏分⻚的?分⻚插件的原理是什么?
    • 14、Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述⼀下动态 sql 的执⾏原理不?
    • 15、Mybatis 是如何将 sql 执⾏结果封装为⽬标对象并返回的?都有哪些映射形式?
    • 16、Mybatis 的 Xml 映射⽂件中,不同的 Xml 映射⽂件,id 是否可以重复?
    • 17、为什么说 Mybatis 是半⾃动 ORM 映射⼯具?它与全⾃动的区别在哪⾥?
    • 18、Mybatis 缓存
    • 19、JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
    • 20、请说说MyBatis的工作原理
    • 21、MyBatis的功能架构是怎样的
    • 22、解释一下Mybatis中命名空间(namespace)的作用?
    • 23、BeanFactory和Applicationcontext的区别
    • 24、BeanFactory和Applicationcontext装载bean的区别
    • 25、Spring单例,为什么controller、service和dao确能保证线程安全?
  • 七、计算机网络
    • 1、简述OSI七层协议
    • 2、简述TCP/IP五层协议
    • 3、物理层有什么作用
    • 4、数据链路层有什么作用
    • 5、网络层有什么作用
    • 6、传输层有什么作用
    • 7、会话层有什么作用
    • 8、表示层有什么作用
    • 9、应用层有什么作用
    • 10、TCP与UDP区别
    • 11、为何TCP可靠
    • 12、为何UDP不可靠
    • 13、简述TCP粘包现象
    • 14、TCP粘包现象处理方法
    • 15、简述TCP协议的滑动窗口
    • 16、TCP三次握手过程
    • 17、为什么TCP握手需要三次,两次行不行?
    • 18、TCP四次挥手过程
    • 19、为什么TCP挥手需要4次
    • 20、简述DNS协议
    • 21、简述HTTP协议
    • 22、简述cookie
    • 23、简述session
    • 24、转发和重定向的区别
    • 25、http与https的区别
    • 26、https的连接过程
    • 27、Get与Post区别
    • 28、浏览器中输入一个网址后,具体发生了什么
    • 29、http请求包含了什么
    • 30、HTTP⻓连接,短连接
    • 31、HTTP是不保存状态的协议,如何保存⽤户状态?
    • 32、URI和URL的区别是什么?
    • 33、Time-wait状态?(==面试题==)
  • 八、操作系统
    • 1、什么是操作系统?请简要概述一下
    • 2、什么是内核态和用户态?
    • 3、并发和并行的区别
    • 4、什么是进程?
    • 5、简述进程间通信方法
    • 6、什么是线程?
    • 7、简述线程和进程的区别和联系
    • 8、进程同步的方法
    • 9、线程同步的方法
    • 10、进程同步与线程同步有什么区别
    • 11、如何解决死锁问题?
    • 12、什么是虚拟地址,什么是物理地址?
    • 13、什么是虚拟内存?
    • 14、进程间的通信方式
    • 15、逻辑(虚拟)地址和物理地址
    • 16、什么是虚拟内存(Virtual Memory)?
  • 九、IO流
    • 1、什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别
    • 2.什么是流,按照传输的单位,分成哪两种流,并且他们的父类叫什么流是指数据的传输
    • 3.流按照传输的方向可以分为哪两种,分别举例说明
    • 4.按照实现功能分为哪两种,分别举例说明
    • 5.BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法
    • 6.什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征
    • 7.如果我要对字节流进行大量的从硬盘读取,要用那个流,为什么
    • 8.如果我要打印出不同类型的数据到数据源,那么最适合的流是那个流,为什么
    • 9.怎么样把我们控制台的输出改成输出到一个文件里面,这个技术叫什么
    • 10.怎么样把输出字节流转换成输出字符流,说出它的步骤
    • 12.把包括基本类型在内的数据和字符串按顺序输出到数据源,或者按照顺序从数据源读入,一般用哪两个流
    • 13.把一个对象写入数据源或者从一个数据源读出来,用哪两个流
    • 14.什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作
    • 15.如果在对象序列化的时候不想给一个字段的数据保存在硬盘上面,采用那个关键字?
    • 16.在实现序列化接口是时候一般要生成一个serialVersionUID字段,它叫做什么,一般有什么用
    • 17.InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值
    • 18.OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思
    • 19.流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?
    • 20.Java中的所有的流可以分为几大类,它们的名字是什么,各代表什么
    • 21.写一段代码读取一个序列化的对象一般使用哪种Stream?
    • 22. io流怎样读取文件的?
    • 23 说说你对io流的理解
    • 24 JAVA的IO流和readLine方法
    • 25 用什么把对象动态的写入磁盘中,写入要实现什么接口。
    • 26 FileInputStream 创建详情,就是怎样的创建不报错,它列出了几种形式!
    • 27 请问你在什么情况下会在你得java代码中使用可序列化? 如何实现java序列化?
    • 28 PrintStream、BufferedWriter、PrintWriter的比较?

一、Redis

1、Redis数据类型

String:字符串类型,最简单的类型
Hash:类似于Map的一种结构。
List:有序列表。
Set: 无序集合。
ZSet:带权值的无序集合,即每个ZSet元素还另有一个数字代表权值,集合通过权值进行排序。

2、什么情况下使用redis

  • 针对热点数据进行缓存
  • 对于特定限时数据的存放
  • 针对带热点权值数据的排序list
  • 分布式锁

3、简述缓存穿透

缓存穿透指缓存和数据库均没有需要查询的数据,攻击者不断发送这种请求,使数据库压力过大。

4、简述缓存穿透的解决方法

  • 在数据库操作访问前进行校验,对不合法请求直接返回。
  • 对于经常被访问的,并且数据库没有的键,缓存层记录键=null。

5、简述缓存击穿

缓存击穿指缓存中没有数据,但数据库中有该数据。一般这种情况指特定数据的缓存时间到期,但由于并发用户访问该数据特别多,因此去数据库去取数据,引起数据库访问压力过大。

6、简述缓存穿透的解决办法

  • 设置热点数据永远不过期。
  • 对并发读数据设置并发锁,降低并发性

7、简述缓存雪崩

缓存雪崩指缓存中一大批数据到过期时间,而从缓存中删除。但该批数据查询数据量巨大,查询全部走数据库,造成数据库压力过大。

8、简述缓存雪崩的解决方法

  • 缓存数据设置随机过期时间,防止同一时间大量数据过期。
  • 设置热点数据永远不过期。
  • 对于集群部署的情况,将热点数据均匀分布在不同缓存中。

9、MySQL与Redis区别

mysql是关系型数据库,并且其将数据存储在硬盘中,读取速度较慢。
redis是非关系型数据库,并且其将数据存储在内存中,读取速度较快。

10、Redis基本数据类型实现原理

字符串:采用类似数组的形式存储
list:采用双向链表进行具体实现
hash:采用hashtable或者ziplist进行具体实现集合:采用intset或hashtable存储
有序集合:采用ziplist或skiplist+hashtable实现

11、Redis快的原因

  1. redis是基于内存的数据库,内存数据读取存储效率远高于硬盘型
  2. redis采用多路复用技术通过采用epoll的非阻塞IO,提升了效率

12、Redis有哪些集群部署方式

  • 主从复制
  • 哨兵模式
  • Cluster集群模式

13、简述主从复制模式

在主从复制中,有主库(Master)节点和从库(Slave)节点两个角色。
从节点服务启动会连接主库,并向主库发送SYNC命令。
主节点收到同步命令,启动持久化工作,工作执行完成后,主节点将传送整个数据库文件到从库,从节点接收到数据库文件数据之后将数据进行加载。此后,主节点继续将所有已经收集到的修改命令,和新的修改命令依次传送给从节点,从节点依次执行,从而达到最终的数据同步。通过这种方式,可以使写操作作用于主库,而读操作作用于从库,从而达到读写分离。

14、简述哨兵模式

哨兵模式监控redis集群中Master的工作的状态。在Master主服务器宕机时,从slave中选择新机器当作master,保证系统高可用。每个哨兵每10秒向主服务器,slave和其他哨兵发送ping。客户端通过哨兵,由哨兵提供可供服务的redis master节点。哨兵只需要配master节点,会自动寻找其对应的slave节点。监控同一master节点的哨兵会自动互联,组成哨兵网络,当任一哨兵发现master连接不上,即开会投票,投票半数以上决定Master下线,并从slave节点中选取master节点。

15、cluster集群

cluster提出了虚拟槽的概念。

  1. redis cluster默认有16384个槽,在集群搭建的时候,需要给节点分配哈希槽尽可能相同数量虚拟槽。
  2. 如果目前redis执行set操作,redis先对这个key经过CRC16 hash运算,并把结果对16384取余,得到槽编号。
  3. 根据槽编号,寻找到其对应的redis节点,在节点上执行hash命令。
  4. 如果此时执行get操作,节点先验证该key对应的槽编号是不是归本节点管,如果是则保存数据。如果不是,则发送正确节点编号给客户端。

16、Redis是如何判断数据是否过期的呢?

Redis 通过⼀个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是⼀个long long类型的整数,这个整数保存了key所 指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。

17、过期的数据的删除策略了解么?

常⽤的过期数据的删除策略就两个(重要!⾃⼰造缓存轮⼦的时候需要格外考虑的东⻄):

  1. 惰性删除 :只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。并且,Redis 底层会通过限制删除操作执⾏的时⻓和频率来减少删除操作对CPU时间的影响。

18、简述Redis淘汰机制

  • noeviction:默认禁止驱逐数据。内存不够使用时,对申请内存的命令报错。
  • volatile-lru:从设置了过期时间的数据集中淘汰最近没使用的数据。
  • volatile-ttl:从设置了过期时间的数据集中淘汰即将要过期的数据。
  • volatile-random:从设置了过期时间的数据中随机淘汰数据。
  • allkeys-lru:淘汰最近没使用的数据。
  • allkeys-random:随机淘汰数据。

19、快照(snapshotting)持久化(RDB)

Redis 可以通过创建快照来获得存储在内存⾥⾯的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本(Redis 主从结构,主要⽤来提⾼ Redis 性能),还可以将快照留在原地以便重启服务器的时候使⽤。快照持久化是 Redis 默认采⽤的持久化⽅式。

20、事务具有四⼤特性: (Redis只支持一致性和隔离性)

  • 原⼦性(Atomicity): 事务是最⼩的执⾏单位,不允许分割。事务的原⼦性确保动作要么全部完成,要么完全不起作⽤;
  • 隔离性(Isolation): 并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据库是独⽴的;
  • 持久性(Durability): ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。
  • ⼀致性(Consistency): 执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的;

21、Redis 事务

Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。使⽤ MULTI命令后可以输⼊多个命令。Redis不会⽴即执⾏这些命令,⽽是将它们放到队列,当调⽤了EXEC命令将执⾏所有命令。

22、你知道有哪些Redis分区实现方案?

  • 客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个 redis节点读取。大多数客户端已经实现了客户端分区。
  • 代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
  • 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

23、Redis如何做内存优化

可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面线程模型。

24、什么是 RedLock

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

  1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
  2. 避免死锁:终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
  3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务缓存异常缓存雪崩

25、如何保证缓存与数据库双写时的数据一致性

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?一般来说,就是如果你的系统不是严格
要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况。要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定 不会出现不一致的情况,串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。

26、Redis常见性能问题和解决方案?

  • Master 最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
  • 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
  • 为了主从复制的速度和连接的稳定性,Slave和Master 好在同一个局域网内。
  • 尽量避免在压力较大的主库上增加从库
  • Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
  • 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题, 实现Slave对Master的替换,也即,如果Master也挂了,可以立马启用Slave1做Master,其他不变。

27、Redis数据类型及其使用场景(面试问题

(1)String–字符串

字符串类型是redis最基础的数据结构,首先键是字符串类型,而且其他几种结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。
使用场景:

  • 缓存功能:字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。
  • 计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能,同时数据可以一步落地到其他的数据源。如:视频播放数系统就是使用redis作为视频播放数计数的基础组件。
  • 共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从redis中集中获取。(比如spring boot可以配置将redis作为分布式session存储)
  • 限速:处于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率。

(2)Hash类型

Hash是一个String类型的field和value之间的映射表,即redis的hash数据类型key(hash表名称)对应的value实际的内部存储结构为一个hashmap,因此hash特别适合存储对象。相当于把一个对象的每个属性存储为String类型,将整个对象存储在hash类型中会占用更少的内存。
当前hashmap的实现方式有两种:当hashmap的成员比较少时,redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会真正采用hashmap结构,这时对应的value
的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的hashmap。
使用场景:
用一个对象来存储用户信息,商品信息,订单信息等等。

(3)list类型

redis的list类型其实就是每个元素都是String类型的双向链表。我们可以从链表的头部和尾部添加或者删除元素。这样的List既可以作为栈,也可以作为队列使用。
列表类型是用来储存多个有序的字符串,列表中的每个字符串成为元素(element),一个列表最多可以储存2的32次方-1个元素,在redis中,可以队列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下表的元素等,列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
应用场景
如好友队列,粉丝队列,消息队列,最新消息排行等

  • 消息队列: redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性消息队列: redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
  • 文章列表:每个用户都有属于自己的文章列表,现在需要分页展示文章列表,此时可以考虑使用列表,列表不但有序,同时支持按照索引范围获取元素。

(4)set集合

集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中不允许有重复的元素,并且集合中的元素是无序的,不能通过索引下标获取元素,redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,并合理的使用好集合类型,能在实际开发中解决很多实际问题。
使用场景
集合有取交集、并集、差集等操作,因此可以求共同好友、共同兴趣、分类标签等。

(5)SortSet

有序集合和集合有着必然的联系,他保留了集合不能有重复成员的特性,但不同得是,有序集合中的元素是可以排序的,但是它和列表的使用索引下标作为排序依据不同的是,它给每个元素设置一个分数,作为排序的依据。(有序集合中的元素不可以重复,但是score可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同)。
使用场景:
排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

28、如何确保redis缓存命中(面试题

(1)缓存命中率的影响因素

1.1 业务场景

缓存适合“读多写少”的业务场景,反之,使用缓存的意义其实并不大,命中率会很低。

1.2 时效性

业务需求决定了对时效性的要求,直接影响到缓存的过期时间和更新策略。时效性要求越低,就越适合缓存。在相同key和相同请求数的情况下,缓存时间越长,命中率会越高。

互联网应用的大多数业务场景下都是很适合使用缓存的。

1.3 缓存粒度

通常情况下,缓存的粒度越小,命中率会越高。

举个实际的例子说明:

当缓存单个对象的时候(例如:单个用户信息),只有当该对象对应的数据发生变化时,我们才需要更新缓存或者让移除缓存。而当缓存一个集合的时候(例如:所有用户数据),其中任何一个对象对应的数据发生变化时,都需要更新或移除缓存。

还有另一种情况,假设其他地方也需要获取该对象对应的数据时(比如其他地方也需要获取单个用户信息),如果缓存的是单个对象,则可以直接命中缓存,反之,则无法直接命中。这样更加灵活,缓存命中率会更高。

1.4 过期时间和更新策略

缓存的更新/过期时间和策略也直接影响到缓存的命中率。此处的缓存过期策略并非Redis自带的定期删除和惰性删除策略,而是根据业务场景优化Key的过期时间和更新策略。

如用户的key信息,如果同时过期,那么多个用户同时查询时,就会落到数据库去,也就是要避免缓存同时失效。当数据发生变化时,直接更新缓存的值会比移除缓存(或者让缓存过期)的命中率更高,当然,系统复杂度也会更高。

1.5 缓存预加载-预热

redis的缓存大部分是从数据库加载的,那么第一次使用数据的时候,redis需要从数据库去加载数据,所以在对用户前,可以提前加载需要的数据到缓存,这样用户在第一次访问的时候就可以直接走缓存而不是去查询数据库。

1.6 缓存击穿和缓存穿透

缓存击穿:
主要是缓存过期时的高并发访问,可以通过加锁,同一key只允许一个连接访问到DB数据库解决
缓存穿透:
一般是访问不存在的key,导致落到数据库(可能数据库也没有),这样会降低缓存命中率
(1)应用访问缓存,假如数据存在,则直接返回数据
(2)数据在redis不存在,则去访问数据库,数据库查询到了直接返回应用,同时把结果写回redis
(3)数据在redis不存在,数据库也不存在,返回空,一般来说空值是不会写入redis的,如果反复请求同一条数据,那么则会发生缓存穿透

解决:可以使用布隆过滤器先判断key是否存在,存在才去redis缓存读取(redis缓存里可能key也不存在,这时候就去数据库读取,没有则返回空)

1.7 缓存容量

要注意缓存容量,太小会触发redis的内存淘汰机制,线上redis一般配置maxmemory-policy allkeys-lru算法来进行内存淘汰,这样有一部分key会被删除,导致缓存穿透,从而降低缓存命中率,因此合理配置缓存容量很有必有。

(2)提高缓存命中率的方法

  • 聚焦在高频访问且时效性要求不高的热点业务上(如字典数据、session、token)
  • 缓存预加载(预热)
  • 增加存储容量
  • 调整缓存粒度
  • 更新缓存

29、redis数据预热

缓存预热的思路

a.提前给redis中嵌入部分数据,再提供服务

b.肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二redis根本就容纳不下所有的数据

c.需要更具当天的具体访问情况,试试统计出频率较高的热数据

d.然后将访问频率较高的热数据写入到redis,肯定是热数据也比较多,我们也得多个服务并行的读取数据去写,并行的分布式的缓存预热

e.然后将嵌入的热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库奔溃了

二、Mysql及其优化

1、引擎

MySQL 当前默认的存储引擎是InnoDB,并且在5.7版本所有的存储引擎中
只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB ⽀持事务。

2、MyISAM和InnoDB区别

  • 是否⽀持⾏级锁 : MyISAM 只有表级锁(table-level locking),⽽InnoDB ⽀持⾏级锁(rowlevel locking)和表级锁,默认为⾏级锁。
  • 是否⽀持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原⼦性,其执⾏速度⽐InnoDB类型更快,但是不提供事务⽀持。但是InnoDB 提供事务⽀持事务,外部键等⾼级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能⼒(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
  • 是否⽀持外键: MyISAM不⽀持,⽽InnoDB⽀持。
  • 是否⽀持MVCC :仅 InnoDB ⽀持。应对⾼并发事务, MVCC⽐单纯的加锁更⾼效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下⼯作;MVCC可以使⽤乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统⼀。

附:
1) 表级锁:MySQL中锁定 粒度最⼤的⼀种锁,对当前操作的整张表加锁,实现简单,资源消耗也⽐᫾少,加锁快,不会出现死锁。其锁定粒度最⼤,触发锁冲突的概率最⾼,并发度最低,MyISAM和InnoDB引擎都⽀持表级锁。
2) 行级锁:MySQL中锁定 粒度最⼩ 的⼀种锁,只针对当前操作的⾏进⾏加锁。⾏级锁能⼤⼤减少数据库操作的冲突。其加锁粒度最⼩,并发度⾼,但加锁的开销也最⼤,加锁慢,会出现死锁。
3)页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

三种锁各有各的特点,若仅从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如WEB应用;行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

3、索引

MySQL索引使⽤的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择BTree索引。MySQL的BTree索引使⽤的是B树中的B+Tree,但对于主要的两种存储引擎的实现⽅式是不同的。
MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“⾮聚簇索引”。
InnoDB: 其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段作为主键,这样会造成主索引频繁分裂。

4、并发事务带来哪些问题?

  • 脏读(Dirty read): 当⼀个事务正在访问数据并且对数据进⾏了修改,⽽这种修改还没有提交到数据库中,这时另外⼀个事务也访问了这个数据,然后使⽤了这个数据。因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  • 丢失修改(Lost to modify): 指在⼀个事务读取⼀个数据时,另外⼀个事务也访问了该数据,那么在第⼀个事务中修改了这个数据后,第⼆个事务也修改了这个数据。这样第⼀个事务内的修改结果就被丢失,因此称为丢失修改。
  • 不可重复读(Unrepeatableread): 指在⼀个事务内多次读同⼀数据。在这个事务还没有结束时,另⼀个事务也访问该数据。那么,在第⼀个事务中的两次读数据之间,由于第⼆个事务的修改导致第⼀个事务两次读取的数据可能不太⼀样。这就发⽣了在⼀个事务内两次读到的数据是不⼀样的情况,因此称为不可重复读。
  • 幻读(Phantom read): 幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1)就会发现多了⼀些原本不存在的记录,就好像发⽣了幻觉⼀样,所以称为幻读。
    不可重复读和幻读区别:
    不可重复读的重点是修改⽐如多次读取⼀条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除⽐如多次读取⼀条记录发现记录增多或减少了。

5、事务隔离级别有哪些?MySQL的默认隔离级别是?

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。
  • REPEATABLE-READ(可重复读): 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
  • SERIALIZABLE(可串⾏化): 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可 重复读以及幻读。
    常见面经整理_第1张图片

6、⼤表优化

(除非单表数据未来会一直上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有大问题的。)
当MySQL单表记录数过⼤时,数据库的CRUD性能会明显下降,⼀些常⻅的优化措施如下:

  • 限定数据的范围
    务必禁⽌不带任何限制数据范围条件的查询语句。⽐如:我们当⽤户在查询订单历史的时候,我们可以控制在⼀个⽉的范围内;
  • 读/写分离
    经典的数据库拆分⽅案,主库负责写,从库负责读;
  • 垂直分区
    根据数据库⾥⾯数据表的相关性进⾏拆分。例如,⽤户表中既有⽤户的登录信息⼜有⽤户的基本信息,可以将⽤户表拆分成两个单独的表,甚⾄放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分,把⼀张列⽐较多的表拆分为多张表。如下图所示,这样来说⼤家应该就更容易理解了。
    常见面经整理_第2张图片

垂直拆分的优点: 可以使得列数据变⼩,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应⽤层进⾏Join来解决。此外,垂直分区会让事务变得更加复杂;

  • ⽔平分区
    保持数据表结构不变,通过某种策略存储数据分⽚。这样每⼀⽚数据分散到不同的表或者库中,达到了分布式的⽬的。 ⽔平拆分可以⽀撑⾮常⼤的数据量。⽔平拆分是指数据表⾏的拆分,表的⾏数超过200万⾏时,就会变慢,这时可以把⼀张的表的数据拆成多张表来存放。 举个例⼦:我们可以将⽤户信息表拆分成多个⽤户信息表,这样就可以避免单⼀表数据量过⼤对性能造成影响。

⽔平拆分可以⽀持⾮常⼤的数据量。需要注意的⼀点是:分表仅仅是解决了单⼀表数据过⼤的问题,但由于表的数据还是在同⼀台机器上,其实对于提升MySQL并发能⼒没有什么意义,所以⽔平拆分最好分库。⽔平拆分能够 ⽀持⾮常⼤的数据量存储,应⽤端改造也少,但分⽚事务难以解决,跨节点Join 性能差,逻辑复杂。

7、解释⼀下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?

池化设计应该不是⼀个新名词。我们常⻅的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。就好⽐你去⻝堂打饭,打饭的⼤妈会先把饭盛好⼏份放那⾥,你来了就直接拿着饭盒加菜即可,不⽤再临时⼜盛饭⼜打菜,效率就⾼了。除了初始化资源,池化设计还包括如下这些特征:池⼦的初始值、池⼦的活跃值、池⼦的最⼤值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。
数据库连接本质就是⼀个 socket 的连接。数据库服务端还要维护⼀些缓存和⽤户权限信息之类的 所以占⽤了⼀些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重⽤这些连接。为每个⽤户打开和维护数据库连接,尤其是对动态数据库驱动的⽹站应⽤程序的请求,既昂贵⼜浪费资源。在连接池中,创建连接后,将其放置在池中,并再次使⽤它,因此不必建⽴新的连接。如果使⽤了所有连接,则会建⽴⼀个新连接并将其添加到池中。连接池还减少了⽤户必须等待建⽴与数据库的连接的时间。

8、分库分表之后,id 主键如何处理?

因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要⼀个全局唯⼀的id 来⽀持。⽣成全局 id 有下⾯这⼏种⽅式:

  • UUID:不适合作为主键,因为太⻓了,并且⽆序不可读,查询效率低。⽐适合⽤于⽣成唯⼀的名字的标示⽐如⽂件的名字。
  • 数据库⾃增 id : 两台数据库分别设置不同步⻓,⽣成不重复ID的策略来实现⾼可⽤。这⽅式⽣成的 id 有序,但是需要独⽴部署数据库实例,成本⾼,还会有性能瓶颈。
  • 利⽤ redis ⽣成 id : 性能好,灵活⽅便,不依赖于数据库。但是,引⼊了新的组件造成系统更加复杂,可⽤性降低,编码更加复杂,增加了系统成本。
  • 推特的snowflake算法
  • 美团的Leaf分布式ID生成系统

9、⼀条SQL语句执⾏得很慢的原因有哪些?

  • 大多数情况下很正常,偶尔很慢,则有如下原因
    (1)、数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
    (2)、执行的时候,遇到锁,如表锁、行锁。
  • 这条 SQL 语句一直执行的很慢,则有如下原因。
    (1)、没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法使用索引。
    (2)、数据库选错了索引。
    一条sql语句执行的很慢的原因

10、Mysql小表驱动大表

在实际操作过程中我们要对两张表的dept_id 都设置索引。在一开始我们就讲了一个优化原则即:小表驱动大表,在我们使用IN 进行关联查询时,通过上面IN 操作的执行顺序,我们是先查询部门表再根据部门表查出来的id 信息查询员工信息。我们都知道员工表肯定会有很多的员工信息,但是部门表一般只会有很少的数据信息,我们事先通过查询部门表信息查询员工信息,以小表(t_dept)的查询结果,去驱动大表(t_emp),这种查询方式是效率很高的,也是值得提倡的。

但是我们使用EXISTS 查询时,首先查询员工表,然后根据部门表的查询条件返回的TRUE 或者 FALSE ,再决定员工表中的信息是否需要保留。这不就是用大的数据表(t_emp) 去驱动小的数据表小的数据表(t_dept)了吗?虽然这种方式也可以查出我们想要的数据,但是这种查询方式是不值得提倡的。
常见面经整理_第3张图片

11、sql 优化分析过程

  • 观察,至少跑1天,看看生产的慢 sql 情况。
  • 开启慢查询日志,设置阀值,比如超过5秒钟就是慢sql,并将它抓取出来。
  • expiain + 慢 sql 分析。
  • show profile
  • 运维经理 or DBA,进行 sql 数据库服务器的参数调优。

(1)通过 show status和应用特点了解各种 SQL的执行频率
通过 SHOW STATUS 可以提供服务器状态信息,也可以使用 mysqladmin extende d-status 命令获得。 SHOW STATUS 可以根据需要显示 session 级别的统计结果和 global级别的统计结果。
(2)定位执行效率较低的SQL语句
可以通过以下两种方式定位执行效率较低的 SQL 语句:
1. 可以通过慢查询日志定位那些执行效率较低的 sql 语句,用 --log-slow-queries[=file_name] 选项启动时, mysqld 写一个包含所有执行时间超过long_query_time 秒的 SQL 语句的日志文件。可以链接到管理维护中的相关章节。
2. 使用 show processlist查看当前MYSQL的线程, 命令慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查 询慢查询日志并不能定位问题,可以使用 show processlist 命令查看当前 MySQL 在进行的线程,包括线程的状态,是否锁表等等,可以实时的查看 SQL 执行情况, 同时对一些锁表操作进行优化。
(3)通过EXPLAIN 分析低效 SQL的执行计划:
通过以上步骤查询到效率低的 SQL 后,我们可以通过 explain 或者 desc 获取MySQL 如何执行 SELECT 语句的信息,包括 select 语句执行过程表如何连接和连接 的次序。

12、Mysql为什么要用B+树作为存储结构

  • B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
  • B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
  • 由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
    MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
    (精简版)
    A. 相对于二叉树,层级更少,搜索效率高;
    B. 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储
    的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
    C. 相对Hash索引,B+tree支持范围匹配及排序操作;

13、Mysql怎么确保原子性?

我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。

可以配合该网站看sql优化:
https://blog.csdn.net/weixin_53601359/article/details/115553449

三、JVM

1、简述JVM内存模型

线程私有的运行时数据区: 程序计数器、Java 虚拟机栈、本地方法栈。
线程共享的运行时数据区:Java 堆、方法区。

2、简述虚拟机栈 (线程私有的)

Java 虚拟机栈用来描述 Java 方法执行的内存模型。线程创建时就会分配一个栈空间,线程结束后栈空间被回收。
栈中元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈、动态链接和返回地址等信息。

3、简述本地方法栈

本地方法栈与虚拟机栈作用相似,不同的是虚拟机栈为虚拟机执行 Java 方法服务,本地方法栈为本地方法服务。可以将虚拟机栈看作普通的java函数对应的内存模型,本地方法栈看作由native关键词修饰的函数对应的内存模型。

Java 虚拟机栈和本地方法栈都会出现两种错误: StackOverFlowError 和 OutOfMemoryError 。

  • StackOverFlowError : 若 Java 虚拟机栈的内存⼤⼩不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最⼤深度的时候,就抛出 StackOverFlowError 错误。
  • OutOfMemoryError : 若 Java 虚拟机堆中没有空闲内存,并且垃圾回收器也⽆法提供更多内存的话。就会抛出 OutOfMemoryError 错误。

4、简述JVM中的堆 (共享区域)

堆主要作用是存放对象实例,Java 里几乎所有对象实例都在堆分配内存,堆也是内存管理中最大的一块。Java的垃圾回收主要就是针对堆这一区域进行。

5、简述方法区 (共享区域)

方法区用于存储被虚拟机加载的类信息、常量、静态变量等数据。

6、简述运行时常量池

运行时常量池存放常量池表,用于存放编译器生成的各种字面量与符号引用。一般除了保存 Class 文件中描述的符号引用外,还会把符号引用翻译的直接引用也存储在运行时常量池。除此之外,也会存放字符串基本类型。

7、简述直接内存

直接内存也称为堆外内存,就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理,而是由操作系统来管理。Java通过DriectByteBuffer对其进行操作,避免了在 Java 堆和 Native堆来回复制数据。

8、简述java创建对象的过程 (!!!重点)

Step1:类加载检查

虚拟机遇到⼀条 new 指令时,⾸先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。

Step2:分配内存

在类加载检查通过后,接下来虚拟机将为新⽣对象分配内存。对象所需的内存⼤⼩在类加载完成后便可确定,为对象分配空间的任务等同于把⼀块确定⼤⼩的内存从 Java 堆中划分出来。分配⽅式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配⽅式由 Java 堆是否规整决定,⽽ Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。

Step5:执⾏ init ⽅法

在上⾯⼯作都完成之后,从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从 Java 程序的视⻆来看,对象创建才刚开始, ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说,执⾏ new 指令之后会接着执⾏ ⽅法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全产⽣出来。

(简化版)

  • 检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查引用代表的类是否已被加载、解析和初始化,如果没有就先执行类加载。
  • 检查通过后虚拟机将为新生对象分配内存。
  • 完成内存分配后虚拟机将成员变量设为零值
  • 设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
  • 执行 init 方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

9、简述JVM给对象分配内存的策略

  • 指针碰撞: 这种方式在内存中放一个指针作为分界指示器将使用过的内存放在一边,空闲的放在另一边,通过指针挪动完成分配。
  • 空闲列表: 对于 Java 堆内存不规整的情况,虚拟机必须维护一个列表记录哪些内存可用,在分配时从列表中找到一块足够大的空间划分给对象并更新列表记录

10、简述java的引用类型

强引用: 被强引用关联的对象不会被回收。一般采用 new 方法创建强引用。
软引用:被软引用关联的对象只有在内存不够的情况下才会被回收。一般采用 SoftReference 类来创建软引用。
弱引用:垃圾收集器碰到即回收,也就是说它只能存活到下一次垃圾回收发生之前。一般采用WeakReference 类来创建弱引用。
虚引用: 无法通过该引用获取对象。唯一目的就是为了能在对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用。

11、简述标记清除算法、标记整理算法和标记复制算法

标记清除算法:先标记需清除的对象,之后统一回收。这种方法效率不高,会产生大量不连续的碎片。
标记整理算法:先标记存活对象,然后让所有存活对象向一端移动,之后清理端边界以外内存 。
标记复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当使用的这块空间用完了,就将存活对象复制到另一块,再把已使用过的内存空间一次清理掉。

12、简述JVM类加载过程

加载:

  1. 通过全类名获取类的二进制字节流.
  2. 将类的静态存储结构转化为方法区的运行时数据结构。3. 在内存中生成类的Class对象,作为方法区数据的入口。
    验证:对文件格式,元数据,字节码,符号引用等验证正确性。
    准备:在方法区内为类变量分配内存并设置为0值。
    解析:将符号引用转化为直接引用。
    初始化:执行类构造器clinit方法,真正初始化。

13、简述双亲委派机制

一个类加载器收到类加载请求之后,首先判断当前类是否被加载过。已经被加载的类会直接返回,如果没有被加载,首先将类加载请求转发给父类加载器,一直转发到启动类加载器,只有当父类加载器无法完成时才尝试自己加载。

14、双亲委派机制的优点

  • 避免类的重复加载。相同的类被不同的类加载器加载会产生不同的类,双亲委派保证了java程序的稳定运行。
  • 保证核心API不被修改。

15、程序计数器

程序计数器是⼀块较⼩的内存空间,可以看作是当前线程所执⾏的字节码的⾏号指示器。字节码解释器⼯作时通过改变这个计数器的值来选取下⼀条需要执⾏的字节码指令,分⽀、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
另外,为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀个独⽴的程序计数器,各线程之间计数器互不影响,独⽴存储,我们称这类内存区域为“线程私有”的内存。
从上⾯的介绍中我们知道程序计数器主要有两个作⽤:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能够知道该线程上次运⾏到哪⼉了。
    常见面经整理_第4张图片

16、如何判断对象是否死亡?(两种⽅法)

堆中⼏乎放着所有的对象实例,对堆垃圾回收前的第⼀步就是要判断哪些对象已经死亡(即不能再被任何途径使⽤的对象)。

  • 引⽤计数法
    给对象中添加⼀个引⽤计数器,每当有⼀个地⽅引⽤它,计数器就加1;当引⽤失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使⽤的。
  • 可达性分析算法
    这个算法的基本思想就是通过⼀系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所⾛过的路径称为引⽤链,当⼀个对象到 GC Roots 没有任何引⽤链相连的话,则证明此对象是不可⽤的。

四、设计模式

1、简述设计模式七大原则

  • 开放封闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
  • 单一职责原则:一个类、接口或方法只负责一个职责,降低代码复杂度以及变更引起的风险。
  • 依赖倒置原则:针对接口编程,依赖于抽象类或接口而不依赖于具体实现类。
  • 接口隔离原则:将不同功能定义在不同接口中实现接口隔离。
  • 里氏替换原则:任何基类可以出现的地方,子类一定可以出现。
  • 迪米特原则:每个模块对其他模块都要尽可能少地了解和依赖,降低代码耦合度。
  • 合成复用原则:尽量使用组合(has-a)/聚合(contains-a)而不是继承(is-a)达到软件复用的目的。

2、简述设计模式的分类

  • 创建型模式:在创建对象的同时隐藏创建逻辑,不使用 new 直接实例化对象。有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式:通过类和接口间的继承和引用实现创建复杂结构的对象。有适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式:通过类之间不同通信方式实现不同行为。有策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

3、简述简单工厂模式

简单工厂模式指由一个工厂对象来创建实例,适用于工厂类负责创建对象较少的情况。例子:Spring 中的 BeanFactory 使用简单工厂模式,产生 Bean 对象。

4、简述工厂模式

工厂方法模式指定义一个创建对象的接口,让接口的实现类决定创建哪种对象,让类的实例化推迟到子类中进行。例子:Spring 的 FactoryBean 接口的 getObject 方法也是工厂方法。

5、简述抽象工厂模式

抽象工厂模式指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们的具体类。例子:
java.sql.Connection 接口。

6、简述代理模式

  • 代理模式为其他对象提供一种代理以控制对这个对象的访问。优点是可以增强目标对象的功能,降低代码耦合度,扩展性好。缺点是在客户端和目标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度。
  • 静态代理:在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:程序运行期间动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

7、简述适配器模式 (IO流用的装饰模式、适配器模式)

适配器模式将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。

8、简述模板模式

模板模式定义了一个操作中的算法的骨架,并将一些步骤延迟到子类,适用于抽取子类重复代码到公共父类。
可以封装固定不变的部分,扩展可变的部分。但每一个不同实现都需要一个子类维护,会增加类的数量。

9、简述装饰器模式

装饰者模式可以动态地给对象添加一些额外的属性或行为,即需要修改原有的功能,但又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。

10、简述观察者模式

观察者模式表示的是一种对象与对象之间具有依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

11、简述单例模式

保证一个类仅有一个实例,并提供一个访问他的全局访问点。优点:单例类只有一个实例、共享资源,全局使用节省创建时间,提高性能。可以用静态内部实现,保证是懒加载就行了,就是使用才会创建实例对象。

12、简述一下你了解的Java设计模式?

所谓设计模式,就是一套被反复使用的代码设计经验的总结(加一个真实的案例以增加说服力)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码的可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。

13、j2ee常用的设计模式?说明工厂模式。

常见面经整理_第5张图片

五、多线程

1、简述线程的可见性

可见性指当一个线程修改了共享变量时,其他线程能够立即得知修改。volatile,synchronized,final都能保证可见性。

2、简述java中volatile关键字作用

  • 保证变量对所有线程的可见性。当一条线程修改了变量值,新值对于其他线程来说是立即可以得知的。
  • 禁止指令重排序优化。使用 volatile 变量进行写操作,汇编指令带有 lock 前缀,相当于一个内存屏障,编译器不会将后面的指令重排到内存屏障之前。

3、java线程的实现方式

  • 实现Runnable接口
  • 继承Thread类。
  • 实现Callable接口

4、简述java线程的状态

线程状态有New, RUNNABLE, BLOCK, WAITING, TIMED_WAITING, THERMINATED

  • NEW:新建状态,线程被创建且未启动,此时还未调用 start 方法。
  • RUNNABLE: 运行状态。其表示线程正在JVM中执行,但是这个执行,不一定真的在跑,也可能在排队等CPU。
  • BLOCKED:阻塞状态。线程等待获取锁,锁还没获得。
  • WAITING: 等待状态。线程内run方法运行完语句Object.wait()/Thread.join()进入该状态。
  • TIMED_WAITING:限期等待。在一定时间之后跳出状态。调用Thread.sleep(long) Object.wait(long) Thread.join(long)进入状态。其中这些参数代表等待的时间。
  • TERMINATED:结束状态。线程调用完run方法进入该状态。

5、简述线程池

没有线程池的情况下,多次创建,销毁线程开销比较大。如果在开辟的线程执行完当前任务后执行接下来任务,复用已创建的线程,降低开销、控制最大并发数。
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。
将任务派发给线程池时,会出现以下几种情况

  1. 核心线程池未满,创建一个新的线程执行任务。
  2. 如果核心线程池已满,工作队列未满,将线程存储在工作队列。
  3. 如果工作队列已满,线程数小于最大线程数就创建一个新线程处理任务。
  4. 如果超过大小线程数,按照拒绝策略来处理任务。

6、简述线程池的状态

  • Running:能接受新提交的任务,也可以处理阻塞队列的任务。
  • Shutdown:不再接受新提交的任务,但可以处理存量任务,线程池处于running时调用shutdown方法,会进入该状态。
  • Stop:不接受新任务,不处理存量任务,调用shutdownnow进入该状态。
  • Tidying:所有任务已经终止了,worker_count(有效线程数)为0。
  • Terminated:线程池彻底终止。在tidying模式下调用terminated方法会进入该状态。

7、简述阻塞队列

阻塞队列是生产者消费者的实现具体组件之一。当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,当阻塞队列满了,往队列添加元素的操作将会被阻塞。具体实现有:

  • ArrayBlockingQueue:底层是由数组组成的有界阻塞队列。
  • LinkedBlockingQueue:底层是由链表组成的有界阻塞队列。
  • PriorityBlockingQueue:阻塞优先队列。
  • DelayQueue:创建元素时可以指定多久才能从队列中获取当前元素
  • SynchronousQueue:不存储元素的阻塞队列,每一个存储必须等待一个取出操作
  • LinkedTransferQueue:与LinkedBlockingQueue相比多一个transfer方法,即如果当前有消费者正等待接收元素,可以把生产者传入的元素立刻传输给消费者。
  • LinkedBlockingDeque:双向阻塞队列。

8、谈一谈ThreadLocal

ThreadLocal 是线程共享变量。ThreadLoacl 有一个静态内部类 ThreadLocalMap,其 Key 是ThreadLocal 对象,值是 Entry 对象,ThreadLocalMap是每个线程私有的。

  • set 给ThreadLocalMap设置值。
  • get 获取ThreadLocalMap。
  • remove 删除ThreadLocalMap类型的对象。
    存在的问题
  • 对于线程池,由于线程池会重用 Thread 对象,因此与 Thread 绑定的 ThreadLocal 也会被重用,造成一系列问题。
  • 内存泄漏。由于 ThreadLocal 是弱引用,但 Entry 的 value 是强引用,因此当 ThreadLocal 被垃圾回收后,value 依旧不会被释放,产生内存泄漏。

9、JAVA中的乐观锁与CAS算法

对于乐观锁,开发者认为数据发送时发生并发冲突的概率不大,所以读操作前不上锁。到了写操作时才会进行判断,数据在此期间是否被其他线程修改。如果发生修改,那就返回写入失败;如果没有被修改,那就执行修改操作,返回修改成功。
乐观锁一般都采用 Compare And Swap(CAS)算法进行实现。顾名思义,该算法涉及到了两个操作,比较(Compare)和交换(Swap)。
CAS 算法的思路如下:

  1. 该算法认为不同线程对变量的操作时产生竞争的情况比较少。
  2. 该算法的核心是对当前读取变量值 E 和内存中的变量旧值 V 进行比较。
  3. 如果相等,就代表其他线程没有对该变量进行修改,就将变量值更新为新值 N。
  4. 如果不等,就认为在读取值 E 到比较阶段,有其他线程对变量进行过修改,不进行任何操作。

10、Synchronized底层实现原理

Java 对象底层都关联一个的 monitor(监视器),使用 synchronized 时 JVM 会根据使用环境找到对象的monitor,根据 monitor 的状态进行加解锁的判断。如果成功加锁就成为该 monitor 的唯一持有者,monitor 在被释放前不能再被其他线程获取。
synchronized在JVM编译后会产生monitorenter 和 monitorexit 这两个字节码指令,获取和释放monitor。这两个字节码指令都需要一个引用类型的参数指明要锁定和解锁的对象,对于同步普通方法,锁是当前实例对象;对于静态同步方法,锁是当前类的 Class 对象;对于同步方法块,锁是synchronized 括号里的对象。
执行 monitorenter 指令时,首先尝试获取对象锁。如果这个对象没有被锁定,或当前线程已经持有锁,就把锁的计数器加 1,执行 monitorexit 指令时会将锁计数器减 1。一旦计数器为 0 锁随即就被释放。

11、Synchronized关键词使用方法

  • 直接修饰某个实例方法
  • 直接修饰某个静态方法
  • 修饰代码块

12、简述java偏向锁

JDK 1.6 中提出了偏向锁的概念。该锁提出的原因是,开发者发现多数情况下锁并不存在竞争,一把锁往往是由同一个线程获得的。偏向锁并不会主动释放,这样每次偏向锁进入的时候都会判断该资源是否是偏向自己的,如果是偏向自己的则不需要进行额外的操作,直接可以进入同步操作。
其申请流程为:

  1. 首先需要判断对象的 Mark Word 是否属于偏向模式,如果不属于,那就进入轻量级锁判断逻辑。否则继续下一步判断;
  2. 判断目前请求锁的线程 ID 是否和偏向锁本身记录的线程 ID 一致。如果一致,继续下一步的判断,如果不一致,跳转到步骤4;
  3. 判断是否需要重偏向。如果不用的话,直接获得偏向锁;
  4. 利用 CAS 算法将对象的 Mark Word 进行更改,使线程 ID 部分换成本线程 ID。如果更换成功,则重偏向完成,获得偏向锁。如果失败,则说明有多线程竞争,升级为轻量级锁。

13、简述轻量级锁

轻量级锁是为了在没有竞争的前提下减少重量级锁出现并导致的性能消耗。
其申请流程为:

  1. 如果同步对象没有被锁定,虚拟机将在当前线程的栈帧中建立一个锁记录空间,存储锁对象目前Mark Word 的拷贝。
  2. 虚拟机使用 CAS 尝试把对象的 Mark Word 更新为指向锁记录的指针
  3. 如果更新成功即代表该线程拥有了锁,锁标志位将转变为 00,表示处于轻量级锁定状态。
  4. 如果更新失败就意味着至少存在一条线程与当前线程竞争。虚拟机检查对象的 Mark Word 是否指向当前线程的栈帧
  5. 如果指向当前线程的栈帧,说明当前线程已经拥有了锁,直接进入同步块继续执行
  6. 如果不是则说明锁对象已经被其他线程抢占。
  7. 如果出现两条以上线程争用同一个锁,轻量级锁就不再有效,将膨胀为重量级锁,锁标志状态变为
  8. 此时Mark Word 存储的就是指向重量级锁的指针,后面等待锁的线程也必须阻塞。

14、线程池类型

  • newCachedThreadPool 可缓存线程池,可设置最小线程数和最大线程数,线程空闲1分钟后自动销毁。
  • newFixedThreadPool 指定工作线程数量线程池。
  • newSingleThreadExecutor 单线程Executor。
  • newScheduleThreadPool 支持定时任务的指定工作线程数量线程池。
  • newSingleThreadScheduledExecutor 支持定时任务的单线程Executor。

15、多线程中的i++线程安全吗?

常见面经整理_第6张图片

16、介绍一下生产者消费者模式?

常见面经整理_第7张图片

17、简述AQS

AQS(AbstractQuenedSynchronizer)抽象的队列式同步器。
AQS是将每一条请求共享资源的线程封装成一个锁队列的一个结点(Node),来实现锁的分配。
AQS是用来构建锁或其他同步组件的基础框架,它使用一个 volatile int state 变量作为共享资源,如果线程获取资源失败,则进入同步队列等待;如果获取成功就执行临界区代码,释放资源时会通知同步队列中的等待线程。
子类通过继承同步器并实现它的抽象方法getState、setState 和 compareAndSetState对同步状态进行更改。

18、线程池有什么好处?常见面经整理_第8张图片

19、sleep()和wait()有什么区别常见面经整理_第9张图片

20、举例说明同步和异步?

21、如何保证线程安全?常见面经整理_第10张图片

22、讲一下非公平锁和公平锁在reetrantlock里的实现?常见面经整理_第11张图片

23、什么是死锁?常见面经整理_第12张图片在这里插入图片描述

24、如何保证不会出现死锁?

25、为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤ run() ⽅法?

new ⼀个 Thread,线程进⼊了新建状态。调⽤ start() ⽅法,会启动⼀个线程并使线程进⼊了就绪状态,当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏ run() ⽅法的内容,这是真正的多线程⼯作。 但是,直接执⾏ run() ⽅法,会把 run()⽅法当成⼀个 main 线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作。
总结: 调⽤ start() ⽅法⽅可启动线程并使线程进⼊就绪状态,直接执⾏ run() ⽅法的话不会以多线程的⽅式执⾏。

26、说说 synchronized 关键字和 volatile 关键字的区别

synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在!

  • volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定⽐ synchronized 关键字要好。但是 volatile 关键字只能⽤于变量⽽ synchronized 关键字可以修饰⽅法以及代码块。
  • volatile 关键字能保证数据的可⻅性,但不能保证数据的原⼦性。 synchronized 关键字两者都能保证。
  • volatile 关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized 关键字解决的是多个线程之间访问资源的同步性。

27、ThreadLocal 了解么?

通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有⾃⼰的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。
ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成存放数据的盒⼦,盒⼦中可以存储每个线程的私有数据。
如果你创建了⼀个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是 ThreadLocal 变量名的由来。他们可以使⽤ get_x0005_ 和 set_x0005_ ⽅法来获取默认值或将其值更改为当前线程所存的副本的值,从⽽避免了线程安全问题。

28、说说怎么创建一个线程池?(面试题)

线程池的创建方法总共有 7 种,但总体来说可分为 2 类:

  • 通过 ThreadPoolExecutor 创建的线程池

  • 通过 Executors 创建的线程池
    线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):

  • Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

  • Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;

  • Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

  • Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;

  • Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

  • Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 添加

  • ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲
    单线程池的意义: 虽然 newSingleThreadExecutor 和 newSingleThreadScheduledExecutor 是单线程池,但提供了工作队列,生命周期管理,工作线程维护等功能。

29、线程池的饱和策略

当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中最老的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

30、线程池调优

  • 设置最大线程数,防止线程资源耗尽;
  • 使用有界队列,从而增加系统的稳定性和预警能力(饱和策略);
  • 根据任务的性质设置线程池大小:CPU密集型任务(CPU个数个线程),IO密集型任务(CPU个数两倍的线程),混合型任务(拆分)。

31、说一下Synchronized修饰静态方法是怎么样?

常见面经整理_第13张图片

32、Synchronized和Lock的区别?

常见面经整理_第14张图片

区别如下:

来源:
lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

异常是否释放锁:
synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

33、锁的分类

Java常见的锁总结
锁是一种多线程同步访问技术。
我们常听到的关于锁的词有:排它锁、共享锁、可重入锁、乐观锁、悲观锁、公平锁、非公平锁、自旋锁、偏向锁、轻量级锁、重量级锁、分段锁等。这些大多是对锁进行类型划分,或者是一种锁的设计思想,彼此之间很多性质有的是兼容的,有的是对立的。
我们常用的Java中的锁有:CAS机制、synchronized、ReentrantLock、ReentrantReadWriteLock

根据锁的性质分类
根据重入和排它性分析:共享锁、可重入锁、排它锁
共享锁:线程可以同时获取锁。ReentrantReadWriteLock对于读锁是共享的。在读多写少的情况下使用共享锁会非常高效。
重入锁:线程获取锁后可以重复执行锁区域。Java提供的锁都是可重入锁。不可重入锁非常容易导致死锁。
排它锁:多线程不可同时获取的锁,与共享锁对立。与重入锁不矛盾可以是并存属性。

根据获取锁的方式:乐观锁、悲观锁
乐观锁:其实是一种采用具有原子性的CAS非加锁机制,保证当前线程原子性执行。
悲观锁:直接加锁进行线程隔离。synchronized、ReentrantLock、ReentrantReadWriteLock的写锁都属于悲观锁
悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

根据获取锁时是否先参与排队:公平锁、非公平锁
公平锁:线程试图获取锁时,先按尝试获取锁的时间顺序排队
非公平锁:线程试图获取锁时,如果当前锁没有线程占有,则跟排队获取锁的线程一起竞争锁而无序按顺序排队,则为非公平锁。如果竞选失败,依然要排队。
非公平锁比较高效,因为公平锁需要有先线程唤起

根据锁的状态划分:偏向锁、轻量级锁、重量级锁
偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。类似于乐观锁。
轻量级锁:当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁:当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

根据锁粒度划分:分段锁等
分段锁:分段锁是一种锁思想,对数据分段加锁已提高并发效率,比如jdk8之前的ConcurrentHashMap,jdk8后采用CAS+synchronized。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
  但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
  分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
synchronized在静态方法上时,锁定的是这个类信息,又称为类锁。
synchronized在普通方法上时,锁定的是这个对象实例,又称为对象锁。
synchronized在代码块上时,锁定的是括号里的对象。

锁消除
JVM会加锁的代码进行逃逸分析,当发现是单线程时,会去掉代码所加的锁,以达到优化。

关于锁要知道的事情
AQS(AbstractQueuedSynchronizer 抽象队列式的同步器)是一种多线程访问共享资源的同步器框架。许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…
CAS(Compare and Swap 比较并交换)是乐观锁技术。底层事Java的sun.misc.Unsafe类,它可以直接操作内存。
获取锁和释放锁都是有资源消耗的,线程的阻塞和唤起也要消耗资源。如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等。所以CAS在并发碰撞少的情况下会优于获取锁。
synchronized是JVM层次实现的,在高并发的情况下性能不如代码层次实现的Lock高效,但是synchronized一直在被优化,现在差距已经不大了,是官方推荐的方式。

六、SSM

1、什么是 Spring 框架?

Spring 是⼀种轻量级开发框架,旨在提⾼开发⼈员的开发效率以及系统的可维护性。
我们⼀般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使⽤这些模块可以很⽅便地协助我们进⾏开发。这些模块是:核⼼容器、数据访问/集成,、Web、AOP(⾯向切⾯编程)、⼯具、消息和测试模块。⽐如:Core Container 中的 Core 组件是Spring 所有组件的核⼼,Beans 组件和 Context 组件是实现IOC和依赖注⼊的基础,AOP组件⽤来实现⾯向切⾯编程。
Spring 官⽹列出的 Spring 的 6 个特征:

  • 核⼼技术 :依赖注⼊(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
  • 测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
  • 数据访问 :事务,DAO⽀持,JDBC,ORM,编组XML。
  • Web⽀持 : Spring MVC和Spring WebFlux Web框架。
  • 集成 :远程处理,JMS,JCA,JMX,电⼦邮件,任务,调度,缓存。
  • 语⾔ :Kotlin,Groovy,动态语⾔。

2、列举一些重要的Spring模块

Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注⼊功能。
Spring Aspects : 该模块为与AspectJ的集成提供⽀持。
Spring AOP :提供了⾯向切⾯的编程实现。
Spring JDBC : Java数据库连接。
Spring JMS :Java消息服务。
Spring ORM : ⽤于⽀持Hibernate等ORM⼯具。
Spring Web : 为创建Web应⽤程序提供⽀持。
Spring Test : 提供了对 JUnit 和 TestNG 测试的⽀持。
常见面经整理_第15张图片

3、谈谈⾃⼰对于 Spring IoC 和 AOP 的理解

IoC
IoC(Inverse of Control:控制反转)是⼀种设计思想,就是 将原本在程序中⼿动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。 IoC 容器是Spring ⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。 在实际项⽬中⼀个 Service 类可能有⼏百甚⾄上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把⼈逼疯。如果利⽤ IoC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度。
常见面经整理_第16张图片

AOP
AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤Cglib ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理,如下图所示:
常见面经整理_第17张图片

使⽤ AOP 之后我们可以把⼀些通⽤功能抽象出来,在需要⽤到的地⽅直接使⽤即可,这样⼤⼤简化了代码量。我们需要增加新功能时也⽅便,这样也提⾼了系统扩展性。⽇志功能、事务管理等等场景都⽤到了 AOP 。
AOP的动态代理思想:JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。JdkProxy类实现了InvocationHandler接口,并实现了接口中的invoke()方法,所有动态代理类所调用的方法都会交由该方法处理。在创建的代理方法createProxy()中,使用了Proxy类的newProxyInstance()方法来创建代理对象。newProxyInstance()方法中包含3个参数,其中第1个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的就是代理类JdkProxy本身。在invoke()方法中,目标类方法执行的前后,会分别执行切面类中的check_Permissions()方法和log()方法。
Spring动态代理中的基本概念
1、关注点(concern)
一个关注点可以是一个特定的问题,概念、或者应用程序的兴趣点。总而言之,应用程序必须达到一个目标
安全验证、日志记录、事务管理都是一个关注点
在oo应用程序中,关注点可能已经被代码模块化了还可能散落在整个对象模型中
2、横切关注点(crosscutting concern)
如何一个关注点的实现代码散落在多个类中或方法中
3、方面(aspect)
一个方面是对一个横切关注点模块化,它将那些原本散落在各处的,
用于实现这个关注点的代码规整在一处
4、建议(advice)通知
advice是point cut执行代码,是方面执行的具体实现
5、切入点(pointcut)
用于指定某个建议用到何处
6、织入(weaving)
将aspect(方面)运用到目标对象的过程
7、连接点(join point)
程序执行过程中的一个点

4、Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运⾏时增强,⽽ AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),⽽ AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了。AspectJ 相⽐于 Spring AOP 功能更加强⼤,但是 Spring AOP 相对来说更简单,如果我们的切⾯⽐᫾少,那么两者性能差异不⼤。但是,当切⾯太多的话,最好选择 AspectJ ,它⽐Spring AOP 快很多。

5、Spring 中的 bean 的作⽤域有哪些?

  • singleton : 唯⼀ bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建⼀个新的 bean 实例。
  • request : 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每⼀次HTTP请求都会产⽣⼀个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作⽤域,仅仅在基于portlet的web应⽤中才有意义,Spring5已经没有了。Portlet是能够⽣成语义代码(例如:HTML)⽚段的⼩型Java Web插件。它们基于portlet容器,可以像servlet⼀样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

6、@Component 和 @Bean 的区别是什么? (面试题)

  • 作⽤对象不同: @Component 注解作⽤于类,⽽ @Bean 注解作⽤于⽅法。
  • @Component 通常是通过类路径扫描来⾃动侦测以及⾃动装配到Spring容器中(我们可以使⽤ @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类⾃动装配到Spring 的 bean 容器中)。 @Bean 注解通常是我们在标有该注解的⽅法中定义产⽣这个bean, @Bean 告诉了Spring这是某个类的示例,当我需要⽤它的时候还给我。
  • @Bean 注解⽐ Component 注解的⾃定义性更强,⽽且很多地⽅我们只能通过 @Bean 注解来注册bean。⽐如当我们引⽤第三⽅库中的类需要装配到 Spring 容器时,则只能通过@Bean 来实现。

7、将⼀个类声明为Spring的 bean 的注解有哪些?

我们⼀般使⽤ @Autowired 注解⾃动装配 bean,要想把类标识成可⽤于 @Autowired 注解⾃动装配的 bean 的类,采⽤以下注解可实现:
@Component :通⽤的注解,可标注任意类为 Spring 组件。如果⼀个Bean不知道属于哪个层,可以使⽤ @Component 注解标注。
@Repository : 对应持久层即 Dao 层,主要⽤于数据库相关操作。
@Service : 对应服务层,主要涉及⼀些复杂的逻辑,需要⽤到 Dao层。
@Controller : 对应 Spring MVC 控制层,主要⽤户接受⽤户请求并调⽤ Service 层返回数
据给前端⻚⾯。

8、Spring中的Bean的生命周期

  • Bean 容器找到配置⽂件中 Spring Bean 的定义。
  • Bean 容器利⽤ Java Reflection API 创建⼀个Bean的实例。
  • 如果涉及到⼀些属性值 利⽤ set() ⽅法设置⼀些属性值。
  • 如果 Bean 实现了 BeanNameAware 接⼝,调⽤ setBeanName() ⽅法,传⼊Bean的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接⼝,调⽤ setBeanClassLoader() ⽅法,传⼊ClassLoader 对象的实例。
  • 与上⾯的类似,如果实现了其他 *.Aware 接⼝,就调⽤相应的⽅法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessBeforeInitialization() ⽅法
  • 如果Bean实现了 InitializingBean 接⼝,执⾏ afterPropertiesSet() ⽅法。
  • 如果 Bean 在配置⽂件中的定义包含 init-method 属性,执⾏指定的⽅法。如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessAfterInitialization() ⽅法
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接⼝,执⾏ destroy() ⽅法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置⽂件中的定义包含 destroy-method 属性,执⾏指定的⽅法。

9、说说对SpringMVC的理解

MVC 是⼀种设计模式,Spring MVC 是⼀款很优秀的 MVC 框架。Spring MVC 可以帮助我们进⾏更简洁的Web层的开发,并且它天⽣与 Spring 框架集成。Spring MVC 下我们⼀般把后端项⽬分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台⻚⾯)。
常见面经整理_第18张图片

10、SpringMVC工作原理

流程说明(重要)

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。
  2. DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Handler 。
  3. 解析到对应的 Handler (也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler 来调⽤真正的处理器开处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对
    象, View 是个逻辑上的 View 。
  6. ViewResolver 会根据逻辑 View 查找实际的 View 。
  7. DispaterServlet 把返回的 Model 传给 View (视图渲染)。
  8. 把 View 返回给请求者(浏览器)
    常见面经整理_第19张图片

11、Spring 框架中⽤到了哪些设计模式?

  • ⼯⼚设计模式 : Spring使⽤⼯⼚模式通过 BeanFactory 、 ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。包装器设计模式 : 我们的项⽬需要连接多个数据库,⽽且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配 Controller 。

12、Spring 事务中的隔离级别有哪⼏种?

TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别,Mysql默认采⽤的 REPEATABLE_READ隔离级别 Oracle 默认采⽤的 READ_COMMITTED隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。

13、Mybatis 是如何进⾏分⻚的?分⻚插件的原理是什么?

答:Mybatis 使⽤ RowBounds 对象进⾏分⻚,它是针对 ResultSet 结果集执⾏的内存分⻚,⽽⾮物理分⻚,可以在 sql 内直接书写带有物理分⻚的参数来完成物理分⻚功能,也可以使⽤分⻚插件来完成物理分⻚。
分⻚插件的基本原理是使⽤ Mybatis 提供的插件接⼝,实现⾃定义插件,在插件的拦截⽅法内拦截待执⾏的 sql,然后重写 sql,根据 dialect ⽅⾔,添加对应的物理分⻚语句和物理分⻚参数。
常见面经整理_第20张图片

14、Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述⼀下动态 sql 的执⾏原理不?

答:Mybatis 动态 sql 可以让我们在 Xml 映射⽂件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能,Mybatis 提供了 9 种动态 sql 标签
trim|where|set|foreach|if|choose|when|otherwise|bind 。
其执⾏原理为,使⽤ OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。

15、Mybatis 是如何将 sql 执⾏结果封装为⽬标对象并返回的?都有哪些映射形式?

答:第⼀种是使⽤ 标签,逐⼀定义列名和对象属性名之间的映射关系。第⼆种是使⽤ sql 列的别名功能,将列别名书写为对象属性名,⽐如 T_NAME AS NAME,对象属性名⼀般是 name,⼩写,但是列名不区分⼤⼩写,Mybatis 会忽略列名⼤⼩写,智能找到与之对应对象属性名,你甚⾄可以写成 T_NAME AS NaMe,Mybatis ⼀样可以正常⼯作。
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使⽤反射给对象的属性逐⼀赋值并返回,那些找不到映射关系的属性,是⽆法完成赋值的。

16、Mybatis 的 Xml 映射⽂件中,不同的 Xml 映射⽂件,id 是否可以重复?

答:不同的 Xml 映射⽂件,如果配置了 namespace,那么 id 可以重复;如果没有配置
namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践⽽已。
原因就是 namespace+id 是作为 Map 的 key 使⽤的,如果没有namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,⾃然 id 就可以重复,namespace 不同,namespace+id ⾃然也就不同。

17、为什么说 Mybatis 是半⾃动 ORM 映射⼯具?它与全⾃动的区别在哪⾥?

答:Hibernate 属于全⾃动 ORM 映射⼯具,使⽤ Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全⾃动的。⽽ Mybatis 在查询关联对象或关联集合对象时,需要⼿动编写 sql 来完成,所以,称之为半⾃动 ORM 映射⼯具。

18、Mybatis 缓存

Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指 SqlSession 级别的缓存,当在同一个SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条SQL。二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的 sqlsession 是可以共享的。
常见面经整理_第21张图片

19、JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接 池可解决此问题。
    解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
  2. Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变 java代码。
    解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
  3. 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需 要和参数一一对应。
    解决: Mybatis自动将java对象映射至sql语句。
  4. 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记 录封装成pojo对象解析比较方便。
    解决:Mybatis自动将sql执行结果映射至java对象。

20、请说说MyBatis的工作原理

1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句, 需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加 载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信 息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对preparedStatement 对象设置参数的过 程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型 和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
常见面经整理_第22张图片

21、MyBatis的功能架构是怎样的

我们把Mybatis的功能架构分为三层:
API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这 些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的 支撑。

22、解释一下Mybatis中命名空间(namespace)的作用?

常见面经整理_第23张图片

23、BeanFactory和Applicationcontext的区别

BeanFactory 可以理解为含有bean集合的工厂类。BeanFactory 包含了种bean的定
义,以便在接收到客户端请求时将对应的bean实例化。
BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。 — FactoryBean和 BeanFactory
从表面上看,application context如同bean factory一样具有bean定义、bean关联
关系的设置,根据请求分发bean的功能。但application context在此基础上还提供
了其他的功能。

  • 提供了支持国际化的文本消息
  • 统一的资源文件读取方式
  • 已在监听器中注册的bean的事件
  • 对beanfactory进行增强,比如可以类型获取所有的容器的对应bean,也可以根据name获取所有的bean

24、BeanFactory和Applicationcontext装载bean的区别

BeanFactory
BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去
实例化;
ApplicationContext
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean
配置lazy-init=true来让Bean延迟实例化;

25、Spring单例,为什么controller、service和dao确能保证线程安全?

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。
实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。
但是如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了。

  • 有状态就是有数据存储功能
  • 无状态就是不会保存数据
    controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

七、计算机网络

1、简述OSI七层协议

OSI七层协议包括:物理层,数据链路层,网络层,运输层,会话层,表示层, 应用层

2、简述TCP/IP五层协议

TCP/IP五层协议包括:物理层,数据链路层,网络层,运输层,应用层

3、物理层有什么作用

主要解决两台物理机之间的通信,通过二进制比特流的传输来实现,二进制数据表现为电流电压上的强弱,到达目的地再转化为二进制机器码。网卡、集线器工作在这一层。

4、数据链路层有什么作用

在不可靠的物理介质上提供可靠的传输,接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路。提供物理地址寻址功能。交换机工作在这一层。

5、网络层有什么作用

将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方,通过路由选择算法为分组通过通信子网选择最佳路径。路由器工作在这一层。

6、传输层有什么作用

传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。

7、会话层有什么作用

建立会话:身份验证,权限鉴定等;
保持会话:对该会话进行维护,在会话维持期间两者可以随时使用这条会话传输局;
断开会话:当应用程序或应用层规定的超时时间到期后,OSI会话层才会释放这条会话。

8、表示层有什么作用

对数据格式进行编译,对收到或发出的数据根据应用层的特征进行处理,如处理为文字、图片、音频、视频、文档等,还可以对压缩文件进行解压缩、对加密文件进行解密等。

9、应用层有什么作用

提供应用层协议,如HTTP协议,FTP协议等等,方便应用程序之间进行通信。

10、TCP与UDP区别

TCP作为面向流的协议,提供可靠的、面向连接的运输服务,并且提供点对点通信
UDP作为面向报文的协议,不提供可靠交付,并且不需要连接,不仅仅对点对点,也支持多播和广播
常见面经整理_第24张图片

11、为何TCP可靠

TCP有三次握手建立连接,四次挥手关闭连接的机制。
除此之外还有滑动窗口和拥塞控制算法。最最关键的是还保留超时重传的机制。
对于每份报文也存在校验,保证每份报文可靠性。

12、为何UDP不可靠

UDP面向数据报无连接的,数据报发出去,就不保留数据备份了。
仅仅在IP数据报头部加入校验和复用。
UDP没有服务器和客户端的概念。
UDP报文过长的话是交给IP切成小段,如果某段报废报文就废了。

13、简述TCP粘包现象

TCP是面向流协议,发送的单位是字节流,因此会将多个小尺寸数据被封装在一个tcp报文中发出去的可能性。
可以简单的理解成客户端调用了两次send,服务器端一个recv就把信息都读出来了。

14、TCP粘包现象处理方法

固定发送信息长度,或在两个信息之间加入分隔符。

15、简述TCP协议的滑动窗口

滑动窗口是传输层进行流量控制的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,防止发送方发送速度过快而导致自己被淹没。

16、TCP三次握手过程

  1. 第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端进入syn_sent状态,等待服务端确认。
  2. 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
  3. 第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输数据了。
    常见面经整理_第25张图片

17、为什么TCP握手需要三次,两次行不行?

不行。TCP进行可靠传输的关键就在于维护一个序列号,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值。
如果只是两次握手, 至多只有客户端的起始序列号能被确认, 服务器端的序列号则得不到确认。

18、TCP四次挥手过程

  1. 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
  2. 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
  3. 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
  4. 第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。
    常见面经整理_第26张图片

19、为什么TCP挥手需要4次

主要原因是当服务端收到客户端的 FIN 数据包后,服务端可能还有数据没发完,不会立即close。
所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了,但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN包后发送 ACK 确认断开信息给服务端。

20、简述DNS协议

DNS协议是基于UDP的应用层协议,它的功能是根据用户输入的域名,解析出该域名对应的IP地址,从而给客户端进行访问。
简述DNS解析过程
1、客户机发出查询请求,在本地计算机缓存查找,若没有找到,就会将请求发送给dns服务器
2、本地dns服务器会在自己的区域里面查找,找到即根据此记录进行解析,若没有找到,就会在本地的缓存里面查找
3、本地服务器没有找到客户机查询的信息,就会将此请求发送到根域名dns服务器
4、根域名服务器解析客户机请求的根域部分,它把包含的下一级的dns服务器的地址返回到客户机的dns服务器地址
5、客户机的dns服务器根据返回的信息接着访问下一级的dns服务器
6、这样递归的方法一级一级接近查询的目标,最后在有目标域名的服务器上面得到相应的IP信息
7、客户机的本地的dns服务器会将查询结果返回给我们的客户机
8、客户机根据得到的ip信息访问目标主机,完成解析过程

21、简述HTTP协议

http协议是超文本传输协议。它是基于TCP协议的应用层传输协议,即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。

22、简述cookie

HTTP 协议本身是无状态的,为了使其能处理更加复杂的逻辑,HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie是由服务端产生的,再发送给客户端保存,当客户端再次访问的时候,服务器可根据cookie辨识客户端是哪个,以此可以做个性化推送,免账号密码登录等等。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。

23、简述session

session用于标记特定客户端信息,存在在服务器的一个文件里。
一般客户端带Cookie对服务器进行访问,可通过cookie中的session id从整个session中查询到服务器记录的关于客户端的信息。

24、转发和重定向的区别

转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器,用户浏览器地址栏URL不变,转发页面和转发到的页面可以共享request里面的数据。
重定向是利用服务器返回的状态码来实现的,如果服务器返回301或者302,浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变,而且不能共享数据。

25、http与https的区别

http所有传输的内容都是明文,并且客户端和服务器端都无法验证对方的身份。
https具有安全性的ssl加密传输协议,加密采用对称加密,
https协议需要到ca申请证书,一般免费证书很少,需要交费。

26、https的连接过程

  1. 浏览器将支持的加密算法信息发给服务器
  2. 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
  3. 客户端(SSL/TLS)解析证书验证证书合法性,生成对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,用服务器的公钥对客户端密钥进行非对称加密。
  4. 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端对称密钥发送给服务器
  5. 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
  6. 服务器将加密后的密文发送给客户端
  7. 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成

27、Get与Post区别

Get:指定资源请求数据,刷新无害,Get请求的数据会附加到URL中,传输数据的大小受到url的限制。
Post:向指定资源提交要被处理的数据。刷新会使数据会被重复提交。post在发送数据前会先将请求头发送给服务器进行确认,然后才真正发送数据。

28、浏览器中输入一个网址后,具体发生了什么

  1. 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
  2. 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
  3. 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
  4. 服务器处理请求,并返回给浏览器
  5. 根据HTTP是否开启长连接,进行TCP的挥手过程
  6. 浏览器根据收到的静态资源进行页面渲染
    常见面经整理_第27张图片

29、http请求包含了什么

包含:请求方法字段、URL字段、HTTP协议版本
产生请求的浏览器类型,请求数据,主机地址。

30、HTTP⻓连接,短连接

在HTTP/1.0中默认使⽤短连接。也就是说,客户端和服务器每进⾏⼀次HTTP操作,就建⽴⼀次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web⻚中包含有其他的Web资源(如JavaScript⽂件、图像⽂件、CSS⽂件等),每遇到这样⼀个Web资源,浏览器就会重新建⽴⼀个HTTP会话。
⽽从HTTP/1.1起,默认使⽤⻓连接,⽤以保持连接特性。使⽤⻓连接的HTTP协议,会在响应头加⼊这⾏代码:

Connection:keep-alive 

在使⽤⻓连接的情况下,当⼀个⽹⻚打开完成后,客户端和服务器之间⽤于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使⽤这⼀条已经建⽴的连接。Keep Alive不会永久保持连接,它有⼀个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现⻓连接需要客户端和服务端都⽀持⻓连接。
HTTP协议的⻓连接和短连接,实质上是TCP协议的⻓连接和短连接。

31、HTTP是不保存状态的协议,如何保存⽤户状态?

HTTP 是⼀种不保存状态,即⽆状态(stateless)协议。也就是说 HTTP 协议⾃身不对请求和响应之间的通信状态进⾏保存。那么我们保存⽤户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作⽤就是通过服务端记录⽤户的状态。典型的场景是购物⻋,当你要添加商品到购物⻋的时候,系统不知道是哪个⽤户操作的,因为 HTTP 协议是⽆状态的。服务端给特定的⽤户创建特定的 Session 之后就可以标识这个⽤户并且跟踪这个⽤户了(⼀般情况下,服务器会在⼀定时间内保存这个 Session,过了时间限制,就会销毁这个Session)。
在服务端保存 Session 的⽅法很多,最常⽤的就是内存和数据库(⽐如是使⽤内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?⼤部分情况下,我们都是通过在 Cookie 中附加⼀个 Session ID 来⽅式来跟踪。

32、URI和URL的区别是什么?

  • URI(Uniform Resource Identifier) 是统⼀资源标志符,可以唯⼀标识⼀个资源。
  • URL(Uniform Resource Location) 是统⼀资源定位符,可以提供该资源的路径。它是⼀种具体的 URI,即 URL 可以⽤来标识⼀个资源,⽽且还指明了如何 locate 这个资源。
    URI的作⽤像身份证号⼀样,URL的作⽤更像家庭住址⼀样。URL是⼀种具体的URI,它不仅唯⼀标识资源,⽽且还提供了定位该资源的信息。

33、Time-wait状态?(面试题)

1.time_wait状态如何产生?
由上面的变迁图,首先调用close()发起主动关闭的一方,在发送最后一个ACK之后会进入time_wait的状态,也就说该发送方会保持2MSL时间之后才会回到初始状态。MSL值得是数据包在网络中的最大生存时间。产生这种结果使得这个TCP连接在2MSL连接等待期间,定义这个连接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。

2.time_wait状态产生的原因

1)为实现TCP全双工连接的可靠释放

由TCP状态变迁图可知,假设发起主动关闭的一方(client)最后发送的ACK在网络中丢失,由于TCP协议的重传机制,执行被动关闭的一方(server)将会重发其FIN,在该FIN到达client之前,client必须维护这条连接状态,也就说这条TCP连接所对应的资源(client方的local_ip,local_port)不能被立即释放或重新分配,直到另一方重发的FIN达到之后,client重发ACK后,经过2MSL时间周期没有再收到另一方的FIN之后,该TCP连接才能恢复初始的CLOSED状态。如果主动关闭一方不维护这样一个TIME_WAIT状态,那么当被动关闭一方重发的FIN到达时,主动关闭一方的TCP传输层会用RST包响应对方,这会被对方认为是有错误发生,然而这事实上只是正常的关闭连接过程,并非异常。

2)为使旧的数据包在网络因过期而消失

为说明这个问题,我们先假设TCP协议中不存在TIME_WAIT状态的限制,再假设当前有一条TCP连接:(local_ip, local_port, remote_ip,remote_port),因某些原因,我们先关闭,接着很快以相同的四元组建立一条新连接。本文前面介绍过,TCP连接由四元组唯一标识,因此,在我们假设的情况中,TCP协议栈是无法区分前后两条TCP连接的不同的,在它看来,这根本就是同一条连接,中间先释放再建立的过程对其来说是“感知”不到的。这样就可能发生这样的情况:前一条TCP连接由local peer发送的数据到达remote peer后,会被该remot peer的TCP传输层当做当前TCP连接的正常数据接收并向上传递至应用层(而事实上,在我们假设的场景下,这些旧数据到达remote peer前,旧连接已断开且一条由相同四元组构成的新TCP连接已建立,因此,这些旧数据是不应该被向上传递至应用层的),从而引起数据错乱进而导致各种无法预知的诡异现象。作为一种可靠的传输协议,TCP必须在协议层面考虑并避免这种情况的发生,这正是TIME_WAIT状态存在的第2个原因。

3)总结
具体而言,local peer主动调用close后,此时的TCP连接进入TIME_WAIT状态,处于该状态下的TCP连接不能立即以同样的四元组建立新连接,即发起active close的那方占用的local port在TIME_WAIT期间不能再被重新分配。由于TIME_WAIT状态持续时间为2MSL,这样保证了旧TCP连接双工链路中的旧数据包均因过期(超过MSL)而消失,此后,就可以用相同的四元组建立一条新连接而不会发生前后两次连接数据错乱的情况。

八、操作系统

1、什么是操作系统?请简要概述一下

操作系统是管理计算机硬件和软件资源的计算机程序,提供一个计算机用户与计算机硬件系统之间的接口。
向上对用户程序提供接口,向下接管硬件资源。
操作系统本质上也是一个软件,作为最接近硬件的系统软件,负责处理器管理、存储器管理、设备管理、文件管理和提供用户接口。

2、什么是内核态和用户态?

为了避免操作系统和关键数据被用户程序破坏,将处理器的执行状态分为内核态和用户态。
内核态是操作系统管理程序执行时所处的状态,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。用户程序运行在用户态,操作系统内核运行在内核态。

3、并发和并行的区别

  • 并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,指令之间交错执行,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率(如降低某个进程的相应时间)。
  • 并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。

4、什么是进程?

进程是操作系统中最重要的抽象概念之一,是资源分配的基本单位,是独立运行的基本单位。
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。
上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程一般由以下的部分组成:

  1. 进程控制块PCB,是进程存在的唯一标志,包含进程标识符PID,进程当前状态,程序和数据地址,进程优先级、CPU现场保护区(用于进程切换),占有的资源清单等。
  2. 程序段
  3. 数据段

5、简述进程间通信方法

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同。
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。

6、什么是线程?

  • 是进程划分的任务,是一个进程内可调度的实体,是CPU调度的基本单位,用于保证程序的实时性,实现进程内部的并发。
  • 线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
  • 每个线程完成不同的任务,但是属于同一个进程的不同线程之间共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

7、简述线程和进程的区别和联系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
  2. 进程在执行过程中拥有独立的地址空间,而多个线程共享进程的地址空间。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
  3. 进程是资源分配的最小单位,线程是CPU调度的最小单位。
  4. 通信:由于同一进程中的多个线程具有相同的地址空间,使它们之间的同步和通信的实现,也变得比较容易。进程间通信 IPC ,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步方法,以保证数据的一致性)。
  5. 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
  6. 进程间不会相互影响;一个进程内某个线程挂掉将导致整个进程挂掉。
  7. 进程适应于多核、多机分布;线程适用于多核。

8、进程同步的方法

操作系统中,进程是具有不同的地址空间的,两个进程是不能感知到对方的存在的。有时候,需要多个进程来协同完成一些任务。
当多个进程需要对同一个内核资源进行操作时,这些进程便是竞争的关系,操作系统必须协调各个进程对资源的占用,进程的互斥是解决进程间竞争关系的方法。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。
当多个进程协同完成一些任务时,不同进程的执行进度不一致,这便产生了进程的同步问题。需要操作系统干预,在特定的同步点对所有进程进行同步,这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。进程互斥本质上也是一种进程同步。
进程的同步方法:

  1. 互斥锁
  2. 读写锁
  3. 条件变量
  4. 记录锁(record locking)
  5. 信号量
  6. 屏障(barrier)

9、线程同步的方法

操作系统中,属于同一进程的线程之间具有相同的地址空间,线程之间共享数据变得简单高效。遇到竞争的线程同时修改同一数据或是协作的线程设置同步点的问题时,需要使用一些线程同步的方法来解决这些问题。
线程同步的方法:

  1. 互斥锁
  2. 读写锁
  3. 条件变量
  4. 信号量
  5. 自旋锁
  6. 屏障(barrier)

10、进程同步与线程同步有什么区别

进程之间地址空间不同,不能感知对方的存在,同步时需要将锁放在多进程共享的空间。而线程之间共享同一地址空间,同步时把锁放在所属的同一进程空间即可。
死锁是怎样产生的?
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。
产生死锁需要满足下面四个条件:

  1. 互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。
  2. 占有并等待条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源。
  3. 非抢占条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放。
  4. 循环等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链。

11、如何解决死锁问题?

解决死锁的方法即破坏产生死锁的四个必要条件之一,主要方法如下:

  1. 资源一次性分配,这样就不会再有请求了(破坏请求条件)。
  2. 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏占有并等待条件)。
  3. 可抢占资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可抢占的条件。
  4. 资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

12、什么是虚拟地址,什么是物理地址?

地址空间是一个非负整数地址的有序集合。
在一个带虚拟内存的系统中,CPU 从一个有N=pow(2,n)个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间(virtual address space),现代系统通常支持 32 位或者 64 位虚拟地址空间。
一个系统还有一个物理地址空间(physical address space),对应于系统中物理内存的M 个字节。
地址空间的概念是很重要的,因为它清楚地区分了数据对象(字节)和它们的属性(地址)。
一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。
主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。

13、什么是虚拟内存?

为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存(VM)。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:

  1. 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
  2. 它为每个进程提供了一致的地址空间,从而简化了内存管理。
  3. 它保护了每个进程的地址空间不被其他进程破坏。

14、进程间的通信方式

  • 管道/匿名管道(Pipes) :⽤于具有亲缘关系的⽗⼦进程间或者兄弟进程之间的通信。
  • 有名管道(Names Pipes) : 匿名管道由于没有名字,只能⽤于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘⽂件的⽅式存在,可以实现本机任意两个进程通信。
  • 信号(Signal) :信号是⼀种⽐᫾复杂的通信⽅式,⽤于通知接收进程某个事件已经发⽣;
  • 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(⽆名管道:只存在于内存中的⽂件;命名管道:存在于实际的磁盘介质或者⽂件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除⼀个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不⼀定要以先进先出的次序读取,也可以按消息的类型读取.⽐ FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载⽆格式字 节流以及缓冲区⼤⼩受限等缺。
  • 信号量(Semaphores) :信号量是⼀个计数器,⽤于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信⽅式主要⽤于解决与同步相关的问题并避免竞争条件。
  • 共享内存(Shared memory) :使得多个进程可以访问同⼀块内存空间,不同进程可以及时看到对⽅进程中对共享内存中数据的更新。这种⽅式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有⽤的进程间通信⽅式。
  • 套接字(Sockets) : 此⽅法主要⽤于在客户端和服务器之间通过⽹络进⾏通信。套接字是⽀持 TCP/IP 的⽹络通信的基本操作单元,可以看做是不同主机之间的进程进⾏双向通信的端点,简单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程。

15、逻辑(虚拟)地址和物理地址

我们编程⼀般只有可能和逻辑地址打交道,⽐如在 C 语⾔中,指针⾥⾯存储的数值就可以理解成为内存⾥的⼀个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体⼀点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。

16、什么是虚拟内存(Virtual Memory)?

这个在我们平时使⽤电脑特别是 Windows 系统的时候太常⻅了。很多时候我们使⽤点开了很多占内存的软件,这些软件占⽤的内存可能已经远远超出了我们电脑本身具有的物理内
存。为什么可以这样呢? 正是因为 虚拟内存 的存在,通过 虚拟内存 可以让程序可以拥有超过系统物理内存⼤⼩的可⽤内存空间。另外,虚拟内存为每个进程提供了⼀个⼀致的、私有的地址空间,它让每个进程产⽣了⼀种⾃⼰在独享主存的错觉(每个进程拥有⼀⽚连续完整的内存空间)。这样会更加有效地管理内存并减少出错。

九、IO流

1、什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别

Bit最小的二进制单位 ,是计算机的操作部分 取值0或者1
Byte是计算机操作数据的最小单位由8位bit组成 取值(-128-127)
Char是用户的可读写的最小单位,在Java里面由16位bit组成 取值(0-65535)

Bit 是最小单位 计算机 只能认识 0或者1

8个字节 是给计算机看的
字符 是看到的东西 一个字符=二个字节

2.什么是流,按照传输的单位,分成哪两种流,并且他们的父类叫什么流是指数据的传输

字节流,字符流
字节流:InputStream OutputStream
字符流:Reader Writer

3.流按照传输的方向可以分为哪两种,分别举例说明

输入输出相对于程序
输入流InputStream
,输出流OutputStream

4.按照实现功能分为哪两种,分别举例说明

节点流,处理流
节点流:OutputStream
处理流: OutputStreamWriter

5.BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法

属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法

6.什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征

节点流 直接与数据源相连,用于输入或者输出
处理流:在节点流的基础上对之进行加工,进行一些功能的扩展
处理流的构造器必须要 传入节点流的子类

7.如果我要对字节流进行大量的从硬盘读取,要用那个流,为什么

BufferedInputStream 使用缓冲流能够减少对硬盘的损伤

8.如果我要打印出不同类型的数据到数据源,那么最适合的流是那个流,为什么

Printwriter 可以打印各种数据类型

9.怎么样把我们控制台的输出改成输出到一个文件里面,这个技术叫什么

SetOut(printWriter,printStream)重定向

10.怎么样把输出字节流转换成输出字符流,说出它的步骤

使用 转换处理流OutputStreamWriter 可以将字节流转为字符流
New OutputStreamWriter(new FileOutputStream(File file));

12.把包括基本类型在内的数据和字符串按顺序输出到数据源,或者按照顺序从数据源读入,一般用哪两个流

DataInputStream DataOutputStream

13.把一个对象写入数据源或者从一个数据源读出来,用哪两个流

ObjectInputStream ObjectOutputStream

14.什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作

对象序列化,将对象以二进制的形式保存在硬盘上
反序列化;将二进制的文件转化为对象读取
实现serializable接口

不想让字段放在硬盘上就加transient

15.如果在对象序列化的时候不想给一个字段的数据保存在硬盘上面,采用那个关键字?

transient关键字

16.在实现序列化接口是时候一般要生成一个serialVersionUID字段,它叫做什么,一般有什么用

是版本号,要保持版本号的一致 来进行序列化

为了防止序列化出错

17.InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值

返回的是所读取的字节的int型(范围0-255)
read(byte [ ] data)将读取的字节储存在这个数组
返回的就是传入数组参数个数

Read 字节读取字节 字符读取字符

18.OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思

write将指定字节传入数据源
Byte b[ ]是byte数组
b[off]是传入的第一个字符
b[off+len-1]是传入的最后的一个字符
len是实际长度

19.流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?

流一旦打开就必须关闭,使用close方法
放入finally语句块中(finally 语句一定会执行)
调用的处理流就关闭处理流
多个流互相调用只关闭最外层的流

20.Java中的所有的流可以分为几大类,它们的名字是什么,各代表什么

分为 字节输入流 InputStream
字节输出流 OutputStream
字符输入流 Reader
字符输出流 Writer
所有流都是这四个流的子类

说下常用的io流

InputStream,OutputStream,
FileInputStream,FileOutputStream,
BufferedInputStream,BufferedOutputStream
Reader,Writer
BufferedReader,BufferedWriter

21.写一段代码读取一个序列化的对象一般使用哪种Stream?

ObjectStream

22. io流怎样读取文件的?

使用File对象获取文件路径,通过字符流Reader加入文件,使用字符缓存流BufferedReader处理Reader,再定义一个字符串,循环遍历出文件。代码如下:
File file = new File(“d:/spring.txt”);
try {
Reader reader = new FileReader(file);
BufferedReader buffered = new BufferedReader(reader);
String data = null;
while((data = buffered.readLine())!=null){
System.out.println(data);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

23 说说你对io流的理解

Io流主要是用来处理输入输出问题,常用的io流有InputStream,OutputStream,Reader,Writer等

24 JAVA的IO流和readLine方法

Java的io流用来处理输入输出问题,readLine是BufferedReader里的一个方法,用来读取一行。

25 用什么把对象动态的写入磁盘中,写入要实现什么接口。

ObjectInputStream,需要实现Serializable接口

26 FileInputStream 创建详情,就是怎样的创建不报错,它列出了几种形式!

FileInputStream是InputStream的子类,通过接口定义,子类实现创建FileInputStream,

27 请问你在什么情况下会在你得java代码中使用可序列化? 如何实现java序列化?

把一个对象写入数据源或者从一个数据源读出来,使用可序列化,需要实现Serializable接口

28 PrintStream、BufferedWriter、PrintWriter的比较?

PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream
BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过write()方法可以将获取到的字符输出,然后通过newLine()进行换行操作。BufferedWriter中的字符流必须通过调用flush方法才能将其刷出去。并且BufferedWriter只能对字符流进行操作。如果要对字节流操作,则使用BufferedInputStream。
PrintWriter的println方法自动添加换行,不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生,PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);

你可能感兴趣的:(面经整理,java,mysql,sql,spring,boot)