背景

985毕业至今刚好一年+四个月,我曾做过两三个月的测试感觉不是很合适,后面选择从事Java后端开发,大学学习过一段时间,还挺香。两个月前秋招还在热火朝天中,内心贼想去大厂试波水。于是乎,字节打电话叫我面试去。不过真真的是傻眼了,果然一句话说的真对:面试造火箭,工作拧螺丝!!整个一面下来表示怀疑人生...没想过一面就能问这么多,疯狂轰炸,连环50问,不得停歇。感觉我这辈子都不会忘记这次面试经历了,这里给大家分享一下腾讯试水的细节,我想,恐怕你也会表示同感。

 

我的面试过程(历经70mins)

  1. 个人履历简述

2. 项目简述

主要突出重难点,我bb了一堆业务逻辑结果人家都不感兴趣

3. SpringAOP实现

JDK动态代理:实现Invocationhandler接口,本质上是new一个继承了所有类上Interface的Proxy对象,然后通过method.invoke进行调用 CGLib动态代理:在内存中动态生成子类对原对象进行代理,无法代理final类以及方法 共同限制:无法代理到当前class当中this引用的嵌套方法

4. AOP用的哪种?

  • 默认用的JDK动态代理

5. JDK动态代理以及CGLib动态代理性能比较

  • JDK走的反射,会多一些反射调用的开销(方法权限验证、调用开销等)

  • CGLib需要创建新对象,在创建新对象上,即初始化时会多一些开销

6. Java的线程池用过吗,具体参数讲一下

Java的线程池是一个三级存储结构,线程先放入核心线程池,满了之后放到缓存队列当中,最后如果缓存队列也满了则扩容新线程,所以参数有: 核心线程数量 缓存队列类型 最大线程数量 线程活跃时间 线程工厂方法(写日志、重命名线程等)

7. 线程池的Execute和Submit区别

Execute执行runnable,Submit可以执行Future,我们一般用countDownLatch+Future来获取所有的线程结果

8. 继续问,还有别的区别吗?

不知道了,后续查了发现区别如下 Execute会在运行期直接抛出异常,Submit之后在调用Future.get的时候才会抛出异常

9. 线程池如何保证当前线程获取池内的worker的时候不产生争用

volatile的state标志这个worker有没有被使用

10. volatile的特性

通过禁止指令重排序来保证内存可见性,实际使用内存屏障实现的

11. 内存屏障分几种?

当时记不得了,回头查了一下如下: LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。 LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。 StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

12. 除了在volatile当中使用了内存屏障,JAVA还有哪里使用了内存屏障

这个真不知道,知道的小伙伴请在评论区指点一二

13. 你之前讲到了CountDownLatch,你知道它的内部实现吗

知道,用的AQS,在state=0的时候才允许所有等待的线程全部通过

14. 简单讲一下AQS

AQS核心设计: 一个volatile int state的状态值,使用volatile保证线程可见性,使用int来提供可重入的多资源能力 双向队列,首节点为执行节点,可以根据执行节点的Node信息判断是ShareLock还是ExclusiveLock,会关联一个执行线程,来提供可重入的判断 加锁的时候若是公平锁则尝试CAS载入队列,若是非公平锁则直接入队列 解锁的时候直接唤醒后继的第一个wait节点

15. 加锁之后AQS是如何响应中断的?

太细节了真不会,之前复习源码没看这么深(结束之后补漏洞)

16. OK问点别的,AQS存在什么实现呢?

用过的ReentranceLock、CountDownLatch

17. 讲讲实现

ReentranceLock通过判断线程是否相同进行冲入 CountDownLatch在state为0的时候才让所有的await通过

18. 听说过ReadWriteLock吗,你之前提到AQS当中只有一个State那你如何用一个State去支撑读写两种状态

一个state是Int,可以分高位给Read,低位给Write,就当个String用了

19. Int几个字节

我居然回答了32个,应该是32位,8位一个byte,共计四个byte

20. 你们用过缓存吗

没有,但是用redis做了分布式锁

21. 你说说下分布式锁怎么做的?

分布式锁也是一个锁,需要满足几个特性,1 可重入 2 可以识别加锁的身份防止ABA问题 3 考虑是否需要续约 key是所需要加上的锁的业务资源唯一编码,value是当前线程的uuid,uuid存在threadLocal内 加锁的时候用的jedis,先设一个过期时间,然后用ex,若不存在key则添加新key,若已经存在则直接失败 解锁用的阿里云企业版的CAD(compareAndDelete),原子比较并解锁,本质是通过lua脚本进行的类似事务操作

22. 除了redis还有什么可以做分布式锁?

Mysql、zookeeper等

23. 如果让你用Mysql做分布式锁你怎么做

新建一张表,主键为需要锁的锁key,col1为线程uuid,col2为ttl时间 加锁的时候在一个事务中选取当前key的record,若存在则判断ttl,若不存在则直接可以插入 解锁的时候直接把record删除即可 起一个定时任务来遍历表,清楚过期键防止无限膨胀

24. zookeeper了解吗

一点点,摄入不深

25. 那我们继续聊聊Redis吧,Redis有什么数据结构?

List,Hash,Set,Zset,List

26. Zset怎么实现的?

跳表+map实现

27. 什么是跳表?

常规链表只有一个next节点,跳表持有多个指向其他链表的指针,可以跨越式的进行查找,时间复杂度是logn

28. 如果我要找一个score为A的节点应该如何去找?

首先在map中找到对应的node排名,然后根据排名在skiplist中进行查找

29. zrange是如何实现的?

这个没想到不应该,查了一下如下: ZRANGE key start stop [WITHSCORES],zrange 就是返回有序集 key 中,指定区间内的成员,而跳表中的元素最下面的一层是有序的(上面的几层就是跳表的索引),按照分数排序,我们只要找出 start 代表的元素,然后向前或者向后遍历 M 次拉出所有数据即可,而找出 start 代表的元素,其实就是在跳表中找一个元素的时间复杂度。跳表中每个节点每一层都会保存到下一个节点的跨度,在寻找过程中可以根据跨度和来求当前的排名,所以查找过程是 O(log(N) 过程,加上遍历 M 个元素,就是 O(log(N)+M),所以 redis 的 zrange 不会像 mysql 的 offset 有比较严重的性能问题。

30. Redis持久化

RDB:快照存储,可以选择是否阻塞,使用场景在数据库上下线、主备复制等情况中 AOF:类似于binlog,每个里面都是一个写事件,是优先读取的策略,支持多策略写入(强同步、按时间刷盘、交由操作系统决定刷盘等),AOF为了防止文件膨胀也支持重写

31. AOF重写的时候会不会block主线程?

不会,没有这个必要,起一个子线程重写完毕之后把手头的buffer在刷进去就行了

32. 在载入的时候是怎么做的

本地起一个client直接读取AOF重放其中的命令

33. Redis有哪些多机部署方案?

经典的主备同步,通过RDB初始化备库然后进行命令传播 Sentinel,实际上是一种容灾机制 cluster,集群部署,使用多机占用slot的方式进行集群服务提供

34. 在主备环境下,如果一个备库中途断链了,重新上线的时候怎么执行同步?

主备各自维护一个写入的Offset,对比差异之后在buffer中读出丢失的命令并进行同步

35. 如果备库的offset过于落后已经不在buffer当中了呢?

直接RDB重新同步 使用AOF来查找对应offset的语句(这个是我猜的)

36. cluster如何做的故障转移?

不知道,估计也是检测到客观下线然后paxos选主

37. Mysql了解吗,里面有哪些锁?

类型分类:共享锁(S),独占锁(X),意向锁(与表锁互斥) 粒度分类:行锁、表锁

38. 行锁怎么实现的?

不知道,这个时候已经有点崩溃了,怎么这么多不知道nnd

39. 讲一下事务隔离级别吧

RU、RC、RR、Serializable

40. 你们用的是哪个隔离级别

mysql默认的是RR,我们改成RC了

41. 在默认隔离级别下会产生幻读问题吗?

会,这是幻读是RR的经典问题之一

42. 描述一下幻读

在T1里Select * From table where id = 1;若不存在该记录则insert id = 1的记录进去,但是在select完毕之后T2事务插入了id=1的record,此时后续insert执行失败,本质上来讲是当前的快照都不支持后续dml语句的执行

43. MVCC机制了解吗?

了解,由undolog支撑的数据隔离机制,主要是为了提供更高的并发度

44. 讲一下原理

每一行record都存在两个隐藏行,一个是当前的事务id,一个是指向undolog的指针 mvcc机制运行 在rr和rc两个隔离级别下 在每次生成ReadView的时候,会将当前的活跃事务ID维护在列表当中,如果访问的Record的ID比最小活跃事务的ID还要小说明之前已经提交了,可以直接读取,如果与最大事务ID还要大就证明该事务在这个快照时没提交,需要根据undolog去找对应的历史版本,如果在最大和最小之间,那么若其为活跃事务则找历史版本,若不是则直接读取 在RC级别下,每次Select都生成新的ReadView,所以能看到不同事物间的提交 在RR级别下,只在第一次Select的时候生成ReadView,所以会产生幻读,因为快照读和真实读的结果不一致

45. 慢sql怎么处理?

捞慢sql日志先分析写的索引是不是有问题或者offset太大了,然后看expain

46. 你关注explain的那些col?

key:真实用到的索引 possible_key:可能用的索引 rows:扫描行数,越大越拉垮 filter:过滤数据比例,这个col可以验证索引有效性 extra:包含是否使用索引、sort是否时filesort等

47. https了解吗?

client发一个随机数给server server发证书+随机数回来 client拆证书找第三方验证证书有效性,取出公钥 client拿公钥加密第三个随机数发server server私钥解密

48. 线上机器cpu100%你怎么处理?

容器化时代,一定要top看下是不是st过高,存在超卖的可能性 如果不是的话top看下哪个进程有问题,然后看这个进程哪个线程吃了cpu jstack直接把线程dump出来然后找对应有问题的线程再分析 也有可能是内存泄漏导致的频繁GC问题,可以拉GClog然后在jmap把heap dump出来看下

49. 你们线上JVM一般调整什么参数?

XMX&XMS固定防止内存抖动 堆空间调整:年轻代Age调整、年轻代eden:s0:s1比例调整 收集器调整:大促前把CMS的预清理次数调低一些,CMS的清理阈值调高一些

50. 反问

什么团队? 做什么业务的?

自我反思

虽说这次是抱着试水的心态去的,但是这一连50问着实是有点傻眼了,而且也发现了自己的很多漏洞,如下:

  • 我的简历过长,难以被面试官抓住重点

  • 项目使用技术栈没有体现出来

  • 涉及相关项目重难点表述不是很清楚,分布式锁、多租户的分库分表以及中间件隔离方案、性能问题排查等

  • 各类技术栈停其实都还留在使用层,没有深入去挖掘

  • 语速太快了,70分钟的面试大大小小回答了50个问题,我感觉放慢点够我回答两轮了

最后总结个人所得(供大家参考学习)

这次一面结束之后我反思很久,发现自己真的是有很多不足和漏洞,所以最近一直在规划自己的学习路线去不足,不论你是复习备战面试还是自己学习,我相信我所说的多少还是有点用处的---复习+学习宝典全

1.1 首先,第一个应该去梳理整个体系的知识大纲

整个体系的知识大纲5个专题:并发编程实战、性能调优实战、Spring全家桶、缓存数据库、分布式&微服务等,这边全以xmind绘画,截图展示,原件可提供 点击传送门即可!!

1.2 其次来看面试专题

我从基础-中级-高级开始一步一步逐步深入,这些面试问题一样都有分类整理(附答案解析)

全部的答案不好上传,题目有点多,完整版请点击传送门即可!!

  • 比如基础部分(列举部分提取):
  1. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同?
  2. 构造器 Constructor 是否可被 override?
  3. String StringBuffer 和 StringBuilder 的区别是什么?String 为什么是不可变的?
  4. 对象的相等与指向他们的引用相等,两者有什么不同?
  5. 重载和重写的区别?
  6. 在一个静态方法内调用一个非静态成员为什么是非法的?
  7. 简述线程,程序、进程的基本概念。以及他们之间关系是什么
  8. 什么是方法的返回值?返回值在类的方法里的作用是什么?
  9. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?
  10. Java 面向对象编程三大特性: 封装 继承 多态
  11. Java 序列化中如果有些字段不想进行序列化 怎么办?
  12. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
  13. 线程有哪些基本状态?
  14. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
  15. 成员变量与局部变量的区别有哪些?

  • 比如中级部分(列举部分提取):
  1. Spring的AOP和IOC是什么?使用场景有哪些?Spring事务,事务的属性,数据库隔离级别
  2. Spring和SpringMVC,MyBatis以及SpringBoot的注解分别有哪些?
  3. SpringCould组件有哪些,它们的作用是什么?微服务的CAP是什么?BASE是什么?
  4. HashMap底层实现原理,红黑树,B+树,B树的结构原理,CAS(比较与交换)实现原理
  5. Redis支持的数据类型以及使用场景,持久化,哨兵机制,缓存击穿,缓存穿透
  6. 线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包
  7. 安全性问题(数据篡改(拿到别人的URL,篡改数据(金额)发送给系统))
  8. 索引使用的限制条件,sql优化有哪些,数据同步问题(缓存,数据库数据同步)
  9. 分布式事务
  10. 堆溢出,栈溢出的出现场景以及解决方案
  11. 悲观锁,乐观锁,读写锁,行锁,表锁,自旋锁,死锁,分布式锁,线程同步锁,公平锁,非公平锁分别是什么?
  12. 初始化Bean对象有几个步骤,它的生命周期
  13. JVM内存模型,算法,垃圾回收器,调优,类加载机制(双亲委派),创建一个对象,这个对象在内存中是怎么分配的?
  14. Dubbo的运行原理,与SpringCould相比它为什么效率要高一些,Zookeeper底层原理
  15. 说出几种MQ之间的区别,以及为什么使用这种MQ,消息重复发送(幂等性),消息发送失败,消息掉包,长时间收不到消息,发送的消息太大造成接收不成功

  • 比如高级部分(消息队列+Redis缓存+分库分表+读写分离+分布式系统+高可用+微服务架构)(列举部分提取)
  1. 为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?
  2. 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
  3. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
  4. redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
  5. 了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
  6. 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
  7. 有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决MySQL 主从同步的延时问题?
  8. 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?
  9. 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
  10. 集群部署时的分布式 session 如何实现?
  11. 服务注册和发现是什么意思?Spring Cloud 如何实现?
  12. 一般实现分布式锁都有哪些方式?使用 redis 如何设计分
  13. 布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
  14. dubbo 的 spi 思想是什么?
  15. 如何设计可以动态扩容缩容的分库分表方案?