以下是一些大厂面试的基本流程,大家可以参考下:
字节跳动
阿里
腾讯
网易游戏
\1. **Java 八股文:**了解常考的题型和回答思路;
\2. 算法:刷 100-200 道题,记住刷题最重要的是要理解其思想,不要死记硬背,碰上原题很难,但
大多数的解题思路是相通的。
\3. **项目:**主要准备最近一家公司所负责的业务和项目:
项目的背景,为啥要做这个项目;
系统的演进之路,有哪几个阶段,每个阶段主要做了什么;
项目中的技术选型,在项目中使用一些工具和框架时的调研,为啥选这个;* 项目的亮点:就是你在项目中做过最牛逼的事,复杂的需求方案设计、性能优化、线上问题处理、
项目重构等等;
\4. **架构设计:**主要是平台化的一些思想、DDD 领域驱动设计思想,随着经验的增加,这块会越来越
重要。
\5. **项目管理:**主要是在主导跨团队的项目时,如何高效的协调好各个团队的工作,使用哪些方法来
保障项目的按时交付。在项目遇到困难时,作为项目负责人如何应对等等。跟架构设计一样,这
块也是随着经验的增加越来越重要。
\6. **通用问题:**几个比较容易被问到的问题是:1)为什么离职;2)在上家公司哪些能力得到了成长;
3)平时怎么学习的?
\7. **问面试官:**每次面试最后面试官一般会问有没有什么想问的,如果不知道问什么,可以问下团队
当前负责的业务是什么?主要面临的挑战是什么?
aop 底层是采用动态代理机制实现的:接口+实现类
理对象。
使用 Cglib 生成一个被代理对象的子类来作为代理。
就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,
只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这
样就不需要修改源代码。
JDK1.8 之前
JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。
HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -
\1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在
元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,
直接覆盖,不相同就通过拉链法解决冲突。
防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。
JDK1.8 之后
22当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据
HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会
执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。
一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的,必须小于 1<<30,即 1073741824。如果容量超出了这个
数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。
JDK7 中的扩容机制
组。
有参构造函数:根据参数确定容量、负载因子、阈值等。
第一次 put 时会初始化数组,其容量变为不小于指定容量的 2 的幂数,然后根据负载因
子确定阈值。
JDK8 的扩容机制
调用 put 方法时,则会开始第一次初始化扩容,长度为 16。
将这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量,
然后让 阈值 = 容量 x 负载因子。
都变为原来的 2 倍时,负载因子还是不变)。
此外还有几个细节需要注意:
首次 put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
不是首次 put,则不再初始化,直接存入数据,然后判断是否需要扩容;
个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,
它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认 Segment
的个数是 16 个。
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红
黑树,在冲突小于一定数量时又退回链表。
(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,
而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使
用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
单凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间
相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在
I/O 密集型任务的应用中,我们可以多配置一些线程。例如:数据库交互,文件上传下载,
网络传输等。IO 密集型,即该任务需要大量的 IO,即大量的阻塞,故需要多配置线程数
G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。
G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的。
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线
程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
了分代的概念。
法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明
确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的
Region(这也就是它的名字 Garbage-First 的由来)
XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息
到指定目录。
同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域。
使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未
清理,时间长了就会内存溢出,可以把改为弱引用
为了达到事务的四大特性,数据库定义了 4 种不同的事务隔离级别:
到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。
据库默认都是该级别 (不重复读),可以阻止脏读,但是幻读或不可重复读仍有可能发
生。
是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏
读、不可重复读以及幻读。
务开始时候的内容是一致,禁止读取到别的事务未提交的数据,会造成幻读。
需要进入锁等待。
查询只承认在事务启动前就已经提交完成的数据。
可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的
时候会有幻读。
分析语句,是否加载了不必要的字段/数据。
分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
查看 SQL 涉及的表结构和索引信息。
25* 如果 SQL 很复杂,优化 SQL 结构。
按照可能的优化点执行表结构变更、增加索引、SQL 改写等操作。
查看优化后的执行时间和执行计划。
如果表数据量太大,考虑分表。
利用缓存,减少查询次数。
缓存穿透
带来压力。
有数据。恶意进行 ddos 攻击。
查询到一个空值。
KEY。Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在,如果布隆过滤器中没有查到
这个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果检测到是恶意攻击,
则拒绝进行服务。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的
数据,定期或根据条件触发更新),这样就不会触发更新。
缓存击穿
问题:某个 KEY 失效的时候,正好有大量并发请求访问这个 KEY。
分析:跟穿透其实很像,属于比较偶然的。
解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策
略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。
缓存雪崩
流量压力传导到数据库上,导致数据库压力过大甚至宕机。
强相关的数据要求同时失效 Redis 宕机
数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合
适,数据要均匀分享,缓存服务器要多台高可用。
据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热
数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从
的部署,当主节点挂掉后,能快速的使用从结点顶上。实现熔断限流机制,对系统进行负
载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取
数据。服务降价:提供默认返回值,或简单的提示信息。
最近最少使用策略 LRU(Least Recently Used)是一种缓存淘汰算法,是一种缓存淘汰机
制。
页面移动到队列头,最近没有使用的页面将被放在队列尾的位置
对应的节点移动到队列的前面,如果需要的页面在内存中,此时需要把这个页面加载到内
存中,简单的说,就是将一个新节点添加到队列前面,并在哈希表中跟新相应的节点地址,
如果队列是满的,那么就从队尾移除一个节点,并将新节点添加到队列的前面
堆内存是指由程序代码自由分配的内存,与栈内存作区分。
在 Java 中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可
以访问堆内存。
存不包括栈内存,也不包括堆外使用的内存。
统实际分配的初始值,而是 GC 先规划好,用到才分配。 专用服务器上需要保持 –Xms
和 –Xmx 一致,否则应用刚启动可能就有好几个 FullGC。当两者配置不一致时,堆内存
扩容可能会导致性能抖动。
业务场景下可以设置。官方建议设置为 -Xmx 的 1/2 ~ 1/4.
限大,此参数无效。
-XX:MaxMetaspaceSize=size, Java8 默认不限制 Meta 空间, 一般不允许设置该选项。
-XX:MaxDirectMemorySize=size,系统可以使用的最大堆外内存,这个参数跟 -
Dsun.nio.MaxDirectMemorySize 效果相同。
XX:ThreadStackSize=1m 等价
栈(后进先出)可以用于字符匹配,数据反转等场景
队列(先进先出)可以用于任务队列,共享打印机等场景
聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。一个表只能有一
个聚簇索引,因为一个表的物理顺序只有一种情况,所以,对应的聚簇索引只能有一个。聚簇
索引的叶子节点就是数据节点,既存储索引值,又在叶子节点存储行数据。
Innodb 创建表后生成的文件有:
frm:创建表的语句
idb:表里面的数据+索引文件
持,最终导致全表扫描;B 树能够在非叶节子点中存储数据,但是这也导致在查询连续数
据时可能会带来更多的随机 I/O,而 B+树的所有叶节点可以通过指针相互连接,能够减
少顺序遍历时产生的额外随机 I/O;
点存不了很多个数据,但是 B+树一个节点能存很多索引,B+树叶子节点存所有的数据。
双向链表
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并
发性能的一种机制。
事务版本号
表的隐藏列
undo log
read view
举个例子:
想 roll back 时,根据 ID 完成精准的删除。
除前的数据 insert 进去。
update 即可。
分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)。
内存不足。
网络速度慢。
是否查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)。
是否返回了不必要的行和列。
锁或者死锁。
I/O 吞吐量小,形成了瓶颈效应。
sp_lock,sp_who,活动的用户查看,原因是读写竞争资源
like 以%开头索引无效,当 like 以&结尾,索引有效。
or 语句前后没有同事使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引
生效。
组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这
个时候索引失效。
得。
行全表扫描。
对索引字段进行计算操作,函数操作时不会使用索引。
当全表扫描速度比索引速度快的时候不会使用索引。
理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放
了 2 亿 5 千万的 keys。我们正在测试一些较大的值。任何 list、set、和 sorted set 都可
以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值。
数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。
针,从而达到快速访问节点的目的。跳跃表支持平均 O(logN)、最坏 O(N)复杂度
的节点查找,还可以通过顺序性操作来批量处理节点。
为了省内存。
全量同步
master 服务器会开启一个后台进程用于将 redis 中的数据生成一个 rdb 文件,与此同时,
服务器会缓存所有接收到的来自客户端的写命令(包含增、删、改),当后台保存进程处理完
毕后,会将该 rdb 文件传递给 slave 服务器,而 slave 服务器会将 rdb 文件保存在磁盘并
通过读取该文件将数据加载到内存,在此之后 master 服务器会将在此期间缓存的
命令通过 redis 传输协议发送给 slave 服务器,然后 slave 服务器将这些命令依次作用于
自己本地的数据集上最终达到数据的一致性。
增量同步
从 redis 2.8 版本以前,并不支持部分同步,当主从服务器之间的连接断掉之后,master 服
务器和 slave 服务器之间都是进行全量数据同步。
从 redis 2.8 开始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始
融入了部分同步的概念。部分同步的实现依赖于在 master 服务器内存中给每个 slave 服务
器维护了一份同步日志和同步标识,每个 slave 服务器在跟 master 服务器进行同步时都会
携带自己的同步标识和上次同步的最后位置。当主从连接断掉之后,slave 服务器隔断时间
(默认 1s)主动尝试和 master 服务器进行连接,如果从服务器携带的偏移量标识还在
master 服务器上的同步备份日志中,那么就从 slave 发送的偏移量开始继续上次的同步操
作,如果 slave 发送的偏移量已经不再 master 的同步备份日志中(可能由于主从之间断掉
的时间比较长或者在断掉的短暂时间内 master 服务器接收到大量的写操作),则必须进行
一次全量更新。在部分同步过程中,master 会将本地记录的同步备份日志中记录的指令依次
发送给 slave 服务器从而达到数据一致。
Redis 主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,
slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,
如不成功,要求从机进行全量同步。
RDB
30RDB 持久化方式,是将 Redis 某一时刻的数据持久化到磁盘中,是一种快照式的持久化方
法。
RDB 优点:
份。
常适用于灾难恢复。
部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化
redis 的性能。
RDB 缺点:
Redis 意外宕机时,会丢失部分数据。
当 Redis 数据量比较大时,fork 的过程是非常耗时的,fork 子进程时是会阻塞的,在这
期间 Redis 是不能响应客户端的请求的。
AOF
AOF 方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行
一遍。
AOF 优点:
使用 AOF 会让你的 Redis 更加持久化。
AOF 文件是一个只进行追加的日志文件,不需要在写入时读取文件。
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写 。
AOF 文件可读性高,分析容易。
AOF 缺点:
对于相同的数据来说,AOF 文件大小通常要大于 RDB 文件。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块
所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统
的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Bean 容器找到配置文件中 Spring Bean 的定义。
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
如果涉及到一些属性值 利用 set()方法设置一些属性值。
如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的
名字。
传入 ClassLoader 对象的实例。
BeanFactory 对象的实例。
与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessBeforeInitialization() 方法
如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessAfterInitialization() 方法
法。
执行指定的方法。
生产者:
方案 1:开启 RabbitMQ 事务(同步,性能差)
方案 2:开启 confirm 模式(异步,性能较好)
MQ:(1)exchange 持久化 (2)queue 持久化 (3)消息持久化
如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入
BeanFactory 对象的实例。
与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessBeforeInitialization() 方法
如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessAfterInitialization() 方法
法。
执行指定的方法。
生产者:
方案 1:开启 RabbitMQ 事务(同步,性能差)
方案 2:开启 confirm 模式(异步,性能较好)
MQ:(1)exchange 持久化 (2)queue 持久化 (3)消息持久化
消费者:关闭自动 ACK